LeetCode_Easy题目
题目一:数组中重复的数字
题目描述:
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例:
1 2 3 4 5
| 输入: [2, 3, 1, 0, 2, 5, 3]
输出: 2 或 3
|
解题思路:
哈希表
利用数据结构特点,容易想到使用哈希表(Set)记录数组的各个数字,当查找到重复数字则直接返回。
算法流程:
复杂度分析:
时间复杂度 O(N)
: 遍历数组使用 O(N) ,HashSet 添加与查找元素皆为 O(1) 。
空间复杂度 O(N)
: HashSet 占用 O(N) 大小的额外空间。
Java代码:
1 2 3 4 5 6 7 8 9 10
| class Solution { public int findRepeatNumber(int[] nums) { Set<Integer> dic = new HashSet<>(); for(int num : nums) { if(dic.contains(num)) return num; dic.add(num); } return -1; } }
|
原地交换
题目说明尚未被充分使用,即 在一个长度为n的数组nums里的所有数字都在0~n-1的范围内
。 此说明含义:数组元素的索引和值是一对多的关系
。因此,可遍历数组并通过交换操作,使元素的索引与值对应(即 nums[i] = inums[i]=i
)。因而,就能通过索引映射对应的值,起到与字典等价的作用。
遍历中,第一次遇到数字 xx 时,将其交换至索引 xx 处;而当第二次遇到数字 xx 时,一定有 nums[x] = xnums[x]=x
,此时即可得到一组重复数字。
算法流程:
遍历数组 nums
,设索引初始值为 i = 0
:
若 nums[i] = i
: 说明此数字已在对应索引位置,无需交换,因此跳过;
若 nums[nums[i]] = nums[i]
: 代表索引 nums[i]
处和索引 i
处的元素值都为 nums[i]
,即找到一组重复值,返回此值 nums[i]
;
否则: 交换索引为 i
和 nums[i]
的元素值,将此数字交换至对应索引位置。
复杂度分析:
时间复杂度 O(N) : 遍历数组使用 O(N) ,每轮遍历的判断和交换操作使用 O(1) 。
空间复杂度 O(1) : 使用常数复杂度的额外空间
Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Solution { public int findRepeatNumber(int[] nums) { int i = 0; while(i < nums.length) { if(nums[i] == i) { i++; continue; } if(nums[nums[i]] == nums[i]) return nums[i]; int tmp = nums[i]; nums[i] = nums[tmp]; nums[tmp] = tmp; } return -1; } }
|
题目二:最长公共前缀
题目描述:
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
。
示例:
1 2 3 4 5 6
| 输入:strs = ["flower","flow","flight"] 输出:"fl"
输入:strs = ["dog","racecar","car"] 输出:"" 解释:输入不存在公共前缀。
|
解题思路:
字符串遍历
将结果初始化为第一个字符串,从第一个字符串开始两两进行比较,找出公共部分。
算法流程:
- 当字符串数组长度为 0 时则公共前缀为空,直接返回
- 令最长公共前缀 ans 的值为第一个字符串,进行初始化
- 遍历后面的字符串,依次将其与 ans 进行比较,两两找出公共前缀,最终结果即为最长公共前缀
- 如果查找过程中出现了 ans 为空的情况,则公共前缀不存在直接返回
复杂度分析:
时间复杂度 O(N)
: N为所有字符串长度之和。
空间复杂度 O(n)
: n为第一个字符串长度。
Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Solution { public String longestCommonPrefix(String[] strs) { if(strs.length == 0) return ""; String ans = strs[0]; for(int i =1;i<strs.length;i++) { int j=0; for(;j<ans.length() && j < strs[i].length();j++) { if(ans.charAt(j) != strs[i].charAt(j)) break; } ans = ans.substring(0, j); if(ans.equals("")) return ans; } return ans; } }
|
比较最大最小字符串(Python)
利用python的max()和min(),在Python里字符串是可以比较的,按照ascII值来进行排序,举例abb, aba,abac,最大为abb,最小为aba。所以只需要比较最大最小的公共前缀就是整个数组的公共前缀。
算法流程:
遍历,找到最长和最短字符串;
提取最长和最短字符串的公共前缀,返回;
复杂度分析:
时间复杂度 O(N) : N为字符串个数 。
Python代码:
1 2 3 4 5 6 7 8
| def longestCommonPrefix(self, strs): if not strs: return "" s1 = min(strs) s2 = max(strs) for i,x in enumerate(s1): if x != s2[i]: return s2[:i] return s1
|
题目三:回文数
题目描述:
给你一个整数 x
,如果 x
是一个回文整数,返回 true
;否则,返回 false
。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。
示例:
1 2 3 4 5 6
| 输入:x = 121 输出:true
输入:x = -121 输出:false 解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
|
解题思路:
字符串遍历
将输入int型数据转化为字符串进行双指针处理。
算法流程:
- 读入数据,判断正负,若为负直接返回False;
- 将数据转化为字符串;
- 设置两个指针,分别从字符串开头和结尾进行遍历比较,若发现不同则返回False;
- 指针汇合,说明字符串为回文串,返回True;
复杂度分析:
时间复杂度 O(N)
: N 为字符串长度 。
空间复杂度 O(N)
: 将int型数据转化为字符串开辟了 N 的额外空间。
Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static boolean test(int num) { String s=Integer.toString(num); for(int i=0,j=s.length()-1;i<j;i++,j--){ if(s.charAt(i)!=s.charAt(j)){ return false; } } return true; }
public static void main(String[] args) { Scanner input=new Scanner(System.in); int num=input.nextInt(); if(num<0){ System.out.println("False"); return; } if(test(num)) { System.out.println("True"); } else { System.out.println("False"); } }
|
数学解法
通过取整和取余操作获取整数中对应的数字进行比较。
算法流程:
- 判断此数的为数,计入
div
;
- 使用除法和取余的方式取出数的第一位和最后一位进行比较,若不相等则返回
False
;
- 使用除法和取余的方式取出此数的中间位,继续循环处理;
- 循环结束,返回
true
复杂度分析:
时间复杂度 O(N) : 遍历数组使用 O(N) 。
Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Solution { public boolean isPalindrome(int x) { if (x < 0) return false; int div = 1; while (x / div >= 10) div *= 10; while (x > 0) { int left = x / div; int right = x % 10; if (left != right) return false; x = (x % div) / 10; div /= 100; } return true; } }
|
题目四:删除数组中的重复项
题目描述:
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例:
1 2 3 4 5
| 输入: [2, 3, 1, 0, 2, 5, 3]
输出: 2 或 3
|
解题思路:
双指针
考虑用 2 个指针,一个在前记作 p
,一个在后记作 q
,通过比较来移动两个指针的位置。
算法流程:
- 比较 p 和 q 位置的元素是否相等。如果相等,q 后移 1 位
- 如果不相等,将 q 位置的元素复制到 p+1 位置上,p 后移一位,q 后移 1 位
- 重复上述过程,直到 q 等于数组长度
- 返回 p + 1,即为新数组长度。
复杂度分析:
时间复杂度 O(N)
: 遍历数组使用 O(N) 。
空间复杂度 O(1)
: 原地操作,没有额外空间。
Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public int removeDuplicates(int[] nums) { if(nums == null || nums.length == 0) return 0; int p = 0; int q = 1; while(q < nums.length){ if(nums[p] != nums[q]){ nums[p + 1] = nums[q]; p++; } q++; } return p + 1; }
|
对双指针算法的优化
当数组本身就没有重复元素时,按照上面的方法,每次比较时 nums[p] 都不等于 nums[q],因此就会将 q 指向的元素原地复制一遍,这个操作其实是不必要的。因此我们可以添加一个小判断,当 q - p > 1 时,才进行复制。
Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public int removeDuplicates(int[] nums) { if(nums == null || nums.length == 0) return 0; int p = 0; int q = 1; while(q < nums.length){ if(nums[p] != nums[q]){ if(q - p > 1){ nums[p + 1] = nums[q]; } p++; } q++; } return p + 1; }
|
题目五:判断两个树相等
题目描述:
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例:
1 2 3 4 5
| 输入: [2, 3, 1, 0, 2, 5, 3]
输出: 2 或 3
|
解题思路:
利用深度优先遍历,当满足终止条件时进行返回,不满足时分别判断左子树和右子树是否相同,其中要注意代码中的短路效应
算法流程:
- 当两棵树的当前节点都为 null 时返回 true
- 当其中一个为 null 另一个不为 null 时返回 false
- 当两个都不为空但是值不相等时,返回 false
- 深度优先搜索
复杂度分析:
时间复杂度 O(N)
: N为树的节点个数 。
Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
class Solution { public boolean isSameTree(TreeNode p, TreeNode q) { if(p == null && q == null) return true; if(p == null || q == null) return false; if(p.val != q.val) return false; return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); } }
|
LeetCode_Middle题目
题目六:三数之和
题目描述:
给你一个包含 n 个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c
,使得 a + b + c = 0
?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
1 2 3 4 5
| 输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]]
输入:nums = [] 输出:[]
|
解题思路:
双指针法:
拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
依然还是在数组中找到 abc 使得a + b +c =0
,我们这里相当于 a = nums[i] b = nums[left] c = nums[right]
。
接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0
就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0
说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
复杂度分析:
时间复杂度 O(N*N)
: 数组遍历 。
Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class Solution { public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> result = new ArrayList<>(); Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) { if (nums[i] > 0) { return result; }
if (i > 0 && nums[i] == nums[i - 1]) { continue; }
int left = i + 1; int right = nums.length - 1; while (right > left) { int sum = nums[i] + nums[left] + nums[right]; if (sum > 0) { right--; } else if (sum < 0) { left++; } else { result.add(Arrays.asList(nums[i], nums[left], nums[right])); while (right > left && nums[right] == nums[right - 1]) right--; while (right > left && nums[left] == nums[left + 1]) left++; right--; left++; } } } return result; } }
|
## 题目七:两数之和 |
### 题目描述: |
给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。 |
请你将两个数相加,并以相同形式返回一个表示和的链表。 |
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。 |
1 2 3 4 5 6
| 输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807.
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9] 输出:[8,9,9,9,0,0,0,1]
|
|
### 解题思路: |
#### 双指针法: |
> 正常的多位数相加,就是从个位相加,逢十进一。题目输入的两个链表正好是反转过来的,链表头正好为个位,所以可以直接使用两个指针,一起从两个链表的相同位进行遍历相加,相加的结果存入一个动态创建的新链表。采用一个变量k来存储进位。本题难点在于如何处理不同位数相加和进位问题。 > |
##### 复杂度分析: |
时间复杂度 O(N) : N为两个链表之中最长的那个长度 。 |
空间复杂度 O(N) : N为两个链表之中最长的那个长度 。 |
##### Java代码: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode head= new ListNode(0,null); ListNode tmp=head; int k=0; while(true){ if(k==1){ tmp.val++; } int a=l1.val+l2.val; if(a+k>=10){ tmp.val+=a-10; k=1; } else{ tmp.val+=a; k=0; }
if(l1.next==null||l2.next==null){ break; } l1=l1.next; l2=l2.next; tmp.next= new ListNode(0,null); tmp=tmp.next; } if(l1.next==null){ while(l2.next!=null){ l2=l2.next; tmp.next= new ListNode(0,null); tmp=tmp.next; tmp.val=tmp.val+l2.val+k; if(tmp.val>=10){ tmp.val-=10; k=1;} else{ k=0; } } } else{ while(l1.next!=null){ l1=l1.next; tmp.next= new ListNode(0,null); tmp=tmp.next; tmp.val=tmp.val+l1.val+k; if(tmp.val>=10){ tmp.val-=10; k=1;} else{ k=0; } } } if(k==1){ tmp.next= new ListNode(0,null); tmp=tmp.next; tmp.val=1; } return head; } }
|
|
题目八:删除链表的倒数第N个节点
题目描述:
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
示例:
1 2 3 4 5
| 输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
输入:head = [1], n = 1 输出:[]
|
解题思路:
双指针法:
仍然采用双指针法来解决。首先设置两个指针指向头结点,使第一个指针向后移动n
位,注意判断边界条件。若下一个节点为空,则直接返回头结点的下一个节点(删除头结点)。然后令两个指针同步向后移动,当前面的指针移动到末尾,删除后面指针的下一个节点,返回头结点。
复杂度分析:
时间复杂度 O(N*N)
: 数组遍历 。
Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode n11=head; ListNode n22=head; for(int i=0;i<n;i++){ if(n11.next!=null){ n11=n11.next; } else{ head=head.next; return head; } } while(n11.next!=null){ n11=n11.next; n22=n22.next; } n22.next=n22.next.next; return head; } }
|
## 题目九:搜索最长回文串 |
### 题目描述: |
给你一个字符串 s ,找到 s 中最长的回文子串 |
示例: |
1 2 3 4 5 6
| 输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
输入:s = "cbbd" 输出:"bb"
|
|
### 解题思路: |
#### 空间换时间: |
> 从每一个位置出发,向两边扩散即可。遇到不是回文的时候结束。举个例子,str = acdbbdaa 我们需要寻找从第一个 b(位置为 33)出发最长回文串为多少。怎么寻找? > > 首先往左寻找与当期位置相同的字符,直到遇到不相等为止。 > > 然后往右寻找与当期位置相同的字符,直到遇到不相等为止。 > > 最后左右双向扩散,直到左和右不相等。 > > 每个位置向两边扩散都会出现一个窗口大小(len)。如果 len>maxLen (用来表示最长回文串的长度)。则更新 maxLen 的值。 > > 因为我们最后要返回的是具体子串,而不是长度,因此,还需要记录一下 maxLen 时的起始位置(maxStart),即此时还要 maxStart=len 。 > > 优化: > > 中心扩散的方法,其实做了很多重复计算。动态规划就是为了减少重复计算的问题。动态规划听起来很高大上。其实说白了就是空间换时间,将计算结果暂存起来,避免重复计算。作用和工程中用 redis 做缓存有异曲同工之妙。 > > 我们用一个 boolean dp[r][r] 表示字符串从 i 到 j 这段是否为回文。试想如果 dp[l][r]=true ,我们要判断 dp[l-1][r+1] 是否为回文。只需要判断字符串在(l-1)和(r+1)两个位置是否为相同的字符,是不是减少了很多重复计算。 > > 进入正题,动态规划关键是找到初始状态和状态转移方程。 > > 初始状态,l=r 时,此时 dp[l][r]=true 。 > > 状态转移方程,dp=true 并且(l-1)和(r+1)两个位置为相同的字符,此时 dp[l-1][r+1]=true 。 |
##### 复杂度分析: |
时间复杂度 O(N*N) : 两重循环遍历 。 |
##### Java代码: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public String longestPalindrome(String s) { if (s == null || s.length() < 2) { return s; } int strLen = s.length(); int maxStart = 0; int maxEnd = 0; int maxLen = 1;
boolean[][] dp = new boolean[strLen][strLen]; acdbbdaa for (int r = 1; r < strLen; r++) { for (int l = 0; l < r; l++) { if (s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1])) { dp[l][r] = true; if (r - l + 1 > maxLen) { maxLen = r - l + 1; maxStart = l; maxEnd = r;
} }
}
} return s.substring(maxStart, maxEnd + 1); }
|
|
题目十:合并区间
题目描述:
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
示例:
1 2 3 4 5 6 7
| 输入:intervals = [[1,3],[2,6],[8,10],[15,18]] 输出:[[1,6],[8,10],[15,18]] 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
输入:intervals = [[1,4],[4,5]] 输出:[[1,5]] 解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
|
解题思路:
见注释
复杂度分析:
时间复杂度 O(Nlogn)
: 一次遍历,排序需要 O(nlogn) 。
Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Solution { public int[][] merge(int[][] intervals) { Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]); int[][] res = new int[intervals.length][2]; int idx = -1; for (int[] interval: intervals) { if (idx == -1 || interval[0] > res[idx][1]) { res[++idx] = interval; } else { res[idx][1] = Math.max(res[idx][1], interval[1]); } } return Arrays.copyOf(res, idx + 1); } }
|
## 题目十一:最长连续序列 |
### 题目描述: |
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。 |
示例: |
1 2 3 4 5 6 7
| 输入:intervals = [[1,3],[2,6],[8,10],[15,18]] 输出:[[1,6],[8,10],[15,18]] 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
输入:intervals = [[1,4],[4,5]] 输出:[[1,5]] 解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
|
|
### 解题思路: |
> 本题的关键在于如何查找到当前元素的左右元素。采用Set数据结构进行查找。首先将数组元素写入hashset表中,然后从一个元素开始依次移除它左右的数字,若移除成功代表数组中确实存在,那么继续查找下一个。采用一个current变量存储当前的区间范围,比较之后将结果存储到currentlongest中。 |
##### 。复杂度分析: |
时间复杂度 O(N) : 一次遍历,hashset查找效率为O(1) 。 |
##### Java代码: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Solution { public int longestConsecutive(int[] nums) { Set<Integer> numsSet = new HashSet<>(); for (Integer num : nums) { numsSet.add(num); } int longest = 0; for (Integer num : nums) { if (numsSet.remove(num)) { int currentLongest = 1; int current = num; while (numsSet.remove(current - 1)) { current--; } currentLongest += (num - current); current = num; while(numsSet.remove(current + 1)) current++; currentLongest += (current - num); longest = Math.max(longest, currentLongest); } } return longest; } }
|
|
题目十二:检查网格中是否存在有效路径
题目描述:
给你一个 m x n 的网格 grid。网格里的每个单元都代表一条街道。grid[i][j] 的街道可以是:
- 1 表示连接左单元格和右单元格的街道。
- 2 表示连接上单元格和下单元格的街道。
- 3 表示连接左单元格和下单元格的街道。
- 4 表示连接右单元格和下单元格的街道。
- 5 表示连接左单元格和上单元格的街道。
- 6 表示连接右单元格和上单元格的街道。
你最开始从左上角的单元格 (0,0) 开始出发,网格中的「有效路径」是指从左上方的单元格 (0,0) 开始、一直到右下方的 (m-1,n-1) 结束的路径。该路径必须只沿着街道走。
注意:你不能变更街道。
如果网格中存在有效的路径,则返回 true
,否则返回 false
。
示例:
1 2 3 4 5 6 7 8 9 10 11
| 输入:grid = [[2,4,3],[6,5,2]] 输出:true 解释:如图所示,你可以从 (0, 0) 开始,访问网格中的所有单元格并到达 (m - 1, n - 1) 。
输入:grid = [[1,2,1],[1,2,1]] 输出:false 解释:如图所示,单元格 (0, 0) 上的街道没有与任何其他单元格上的街道相连,你只会停在 (0, 0) 处。
输入:grid = [[1,1,2]] 输出:false 解释:你会停在 (0, 1),而且无法到达 (0, 2) 。
|
解题思路:
采用DFS优先搜索来防止死胡同的情况。每个单元格下一步要走向哪,依据的是当前单元格的数字(1~6),而且,能不能到达当前单元格指向的两个方向的下一个单元格,又依赖于下一个单元格是否有口与当前单元格连通,举个例子吧。假设当前单元格是1,那么可以往左走,也可以往右走,如果往左走的话,则只有(1, 4, 6)可以接的上去。右边与其他数字同理。
。复杂度分析:
时间复杂度 O(N*N)
: 深度优先搜索 。
Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| class Solution { boolean [][]vis; public boolean hasValidPath(int[][] grid) { vis = new boolean[grid.length][grid[0].length]; dfs(grid,0,0); return vis[grid.length-1][grid[0].length-1]; } public void dfs(int[][] grid , int x , int y){ if(vis[x][y]) return; vis[x][y] = true; if(grid[x][y]==1){ if(isok(grid,x,y-1) && (grid[x][y-1]==1 || grid[x][y-1]==4 || grid[x][y-1]==6)) dfs(grid,x,y-1); if(isok(grid,x,y+1) && (grid[x][y+1]==1 || grid[x][y+1]==3 || grid[x][y+1]==5)) dfs(grid,x,y+1); }else if(grid[x][y]==2){ if(isok(grid,x-1,y) && (grid[x-1][y]==2 || grid[x-1][y]==3 || grid[x-1][y]==4)) dfs(grid,x-1,y); if(isok(grid,x+1,y) && (grid[x+1][y]==2 || grid[x+1][y]==5 || grid[x+1][y]==6)) dfs(grid,x+1,y); }else if(grid[x][y]==3){ if(isok(grid,x,y-1) && (grid[x][y-1]==1 || grid[x][y-1]==4 || grid[x][y-1]==6)) dfs(grid,x,y-1); if(isok(grid,x+1,y) && (grid[x+1][y]==2 || grid[x+1][y]==5 || grid[x+1][y]==6)) dfs(grid,x+1,y); }else if(grid[x][y]==4){ if(isok(grid,x,y+1) && (grid[x][y+1]==1 || grid[x][y+1]==3 || grid[x][y+1]==5)) dfs(grid,x,y+1); if(isok(grid,x+1,y) && (grid[x+1][y]==2 || grid[x+1][y]==5 || grid[x+1][y]==6)) dfs(grid,x+1,y); }else if(grid[x][y]==5){ if(isok(grid,x,y-1) && (grid[x][y-1]==1 || grid[x][y-1]==4 || grid[x][y-1]==6)) dfs(grid,x,y-1); if(isok(grid,x-1,y) && (grid[x-1][y]==2 || grid[x-1][y]==3 || grid[x-1][y]==4)) dfs(grid,x-1,y); }else if(grid[x][y]==6){ if(isok(grid,x,y+1) && (grid[x][y+1]==1 || grid[x][y+1]==3 || grid[x][y+1]==5)) dfs(grid,x,y+1); if(isok(grid,x-1,y) && (grid[x-1][y]==2 || grid[x-1][y]==3 || grid[x-1][y]==4)) dfs(grid,x-1,y); } } public boolean isok(int [][]grid , int x,int y){ return !(x<0 || x>grid.length-1 || y<0 || y>grid[0].length-1 || vis[x][y]); } }
|
## 题目十三:猜数字游戏 |
### 题目描述: |
你在和朋友一起玩 猜数字(Bulls and Cows)游戏,该游戏规则如下: |
写出一个秘密数字,并请朋友猜这个数字是多少。朋友每猜测一次,你就会给他一个包含下述信息的提示: |
猜测数字中有多少位属于数字和确切位置都猜对了(称为 "Bulls" , 公牛), 有多少位属于数字猜对了但是位置不对(称为 "Cows" , 奶牛)。也就是说,这次猜测中有多少位非公牛数字可以通过重新排列转换成公牛数字。 给你一个秘密数字 secret 和朋友猜测的数字 guess ,请你返回对朋友这次猜测的提示。 |
提示的格式为 "xAyB" ,x 是公牛个数, y 是奶牛个数,A 表示公牛,B 表示奶牛。 |
请注意秘密数字和朋友猜测的数字都可能含有重复数字。 |
示例: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 输入: secret = "1807", guess = "7810" 输出: "1A3B" 解释: 数字和位置都对(公牛)用 '|' 连接,数字猜对位置不对(奶牛)的采用斜体加粗标识。 "1807" | "7810"
输入: secret = "1123", guess = "0111" 输出: "1A1B" 解释: 数字和位置都对(公牛)用 '|' 连接,数字猜对位置不对(奶牛)的采用斜体加粗标识。 "1123" "1123" | or | "0111" "0111" 注意,两个不匹配的 1 中,只有一个会算作奶牛(数字猜对位置不对)。通过重新排列非公牛数字,其中仅有一个 1 可以成为公牛数字。
|
|
### 解题思路: |
> 根据题意,对于公牛,需要满足数字和确切位置都猜对。我们可以遍历 secret 和 guess,统计满足 secret[i]=guess[i] 的下标个数,即为公牛的个数。 > > 对于奶牛,需要满足数字猜对但是位置不对。我们可以在 secret[i]=guess[i] 时,分别统计 secret 和 guess 的各个字符的出现次数,记在两个长度为 1010 的数组中。根据题目所述的「这次猜测中有多少位非公牛数字可以通过重新排列转换成公牛数字」,由于多余的数字无法匹配,对于 0 到 9 的每位数字,应取其在 secret 和 guess 中的出现次数的最小值。将每位数字出现次数的最小值累加,即为奶牛的个数。 > |
##### 。复杂度分析: |
时间复杂度:O(N) ,其中 N 是字符串 secret 的长度。 |
空间复杂度:O(C) 。需要常数个空间统计字符出现次数,由于我们统计的是数字字符,因此 C=10。 |
##### Java代码: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Solution { public String getHint(String secret, String guess) { int bulls = 0; int[] cntS = new int[10]; int[] cntG = new int[10]; for (int i = 0; i < secret.length(); ++i) { if (secret.charAt(i) == guess.charAt(i)) { ++bulls; } else { ++cntS[secret.charAt(i) - '0']; ++cntG[guess.charAt(i) - '0']; } } int cows = 0; for (int i = 0; i < 10; ++i) { cows += Math.min(cntS[i], cntG[i]); } return Integer.toString(bulls) + "A" + Integer.toString(cows) + "B"; } }
|
|