赞
踩
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
解析
使用 Hash 表记录所有已遍历元素及对应的下标,遍历的同时查找满足条件的数字是否已遍历,若找到返回即可。
代码
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map = new HashMap<>();
int[] res = new int[0];
for(int i = 0; i < nums.length; i++){
if(map.containsKey(target - nums[i])) return new int[]{i, map.get(target - nums[i])};
map.put(nums[i],i);
}
return res;
}
}
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
解析
按位模拟加法,注意循环条件不要忘记进位。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { int cur = 0; int carry = 0;//进位 ListNode dummy = new ListNode(); ListNode helper = dummy; while(l1 != null || l2 != null || carry != 0){ int v1 = l1 == null ? 0 : l1.val; int v2 = l2 == null ? 0 : l2.val; int sum = v1 + v2 + carry; cur = sum % 10; carry = sum / 10; helper.next = new ListNode(cur); helper = helper.next; if(l1 != null) l1 = l1.next; if(l2 != null) l2 = l2.next; } return dummy.next; } }
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
解析
使用一个数组记录每个字符出现次数,在遍历的同时移动窗口左边界,最后返回窗口长度的最大值即可。
注:子串是连续的,子序列是不连续的
代码
class Solution { public int lengthOfLongestSubstring(String s) { int[] map = new int[128]; int res = 0; // i : 窗口右边界 j : 窗口左边界 for(int i = 0 , j = 0;i < s.length(); i++){ map[s.charAt(i)]++; while(map[s.charAt(i)] > 1){ //重复 map[s.charAt(j++)]--;//窗口左移 } res = Math.max(res, i - j + 1);// i - j + 1是窗口的长度 } return res; } }
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
解析
本题可以抽象为:给定两个数组 A、B,如何找到从小到大排列的第 K 个数字,而中位数存在 K = K -1的下标映射关系
代码
class Solution { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int n = nums1.length + nums2.length; if(n % 2 == 0){ return (findKth(nums1, 0, nums2, 0, n / 2) + findKth(nums1, 0, nums2, 0, n / 2 + 1)) / 2.0; }else{ return findKth(nums1, 0, nums2, 0, n / 2 + 1); } } //i: nums1的起始位置 j: nums2的起始位置 public int findKth(int[] nums1, int i, int[] nums2, int j, int k){ if( i >= nums1.length) return nums2[j + k - 1];//nums1为空数组 if( j >= nums2.length) return nums1[i + k - 1];//nums2为空数组 if(k == 1){ return Math.min(nums1[i], nums2[j]); } int midVal1 = (i + k / 2 - 1 < nums1.length) ? nums1[i + k / 2 - 1] : Integer.MAX_VALUE; int midVal2 = (j + k / 2 - 1 < nums2.length) ? nums2[j + k / 2 - 1] : Integer.MAX_VALUE; if(midVal1 < midVal2){ return findKth(nums1, i + k / 2, nums2, j , k - k / 2); }else{ return findKth(nums1, i, nums2, j + k / 2 , k - k / 2); } } }
给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”
解析
中心扩展法,枚举回文子串的中心位置,每次循环需要分奇数和偶数两种情况。
题解
class Solution { public String longestPalindrome(String s) { int l = 0; int r = 0; int maxLen = 0; for(int i = 0; i < s.length(); i++){ int len = Math.max(center(s, i, i), center(s, i, i + 1)); if(len > maxLen){ l = i - (len - 1) / 2; r = i + len / 2; maxLen = len; } } return s.substring(l, r + 1); } //中心扩散法 private int center(String s, int left, int right){ while(left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){ left--; right++; } return right - left - 1; } }
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
示例 1:
输入:s = “aa”, p = “a”
输出:false
解释:“a” 无法匹配 “aa” 整个字符串。
示例 2:
输入:s = “aa”, p = “a*”
输出:true
解释:因为 ‘*’ 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 ‘a’。因此,字符串 “aa” 可被视为 ‘a’ 重复了一次。
示例 3:
输入:s = “ab”, p = “."
输出:true
解释:".” 表示可匹配零个或多个(‘*’)任意字符(‘.’)。
解析
使用动态规划来记录前面的匹配信息,
1、状态表示
f[i][j] 表示 s的前i个字符与 p中的前j个字符是否能够匹配
2、状态计算
p
[
j
]
≠
∗
⟶
f
(
i
,
j
)
=
f
(
i
−
1
,
j
−
1
)
&
&
match
(
i
,
j
)
p[j] \neq * \longrightarrow f(i, j)=f(i-1, j-1) \& \& \operatorname{match}(i, j)
p[j]=∗⟶f(i,j)=f(i−1,j−1)&&match(i,j)
p
[
j
]
=
∗
⟶
f
(
i
,
j
)
=
f
(
i
,
j
−
2
)
∥
f
(
i
−
1
,
j
−
2
)
&
&
match
(
i
,
j
−
1
)
∥
f
(
i
−
2
,
j
−
2
)
&
&
match
(
i
−
1
,
j
−
1
)
&
&
match
(
i
,
j
−
1
)
⋯
=
f
(
i
,
j
−
2
)
∥
f
(
i
−
1
,
j
)
&
&
match
(
i
,
j
−
1
)
p[j]=* \longrightarrow \\ f(i, j)=f(i, j-2)\|f(i-1, j-2) \& \& \operatorname{match}(i, j-1)\| f(i-2, j-2) \& \& \operatorname{match}(i-1, j-1) \& \& \operatorname{match}(i, j-1) \cdots \\ =f(i, j-2) \| f(i-1, j) \& \& \operatorname{match}(i, j-1)
p[j]=∗⟶f(i,j)=f(i,j−2)∥f(i−1,j−2)&&match(i,j−1)∥f(i−2,j−2)&&match(i−1,j−1)&&match(i,j−1)⋯=f(i,j−2)∥f(i−1,j)&&match(i,j−1)
即
p
[
j
]
=
∗
⟶
f
(
i
,
j
)
=
f
(
i
,
j
−
2
)
∥
f
(
i
−
1
,
j
)
&
&
match
(
i
,
j
−
1
)
p[j]=* \longrightarrow \\ f(i, j)=f(i, j-2)\|f(i-1, j) \& \& \operatorname{match}(i, j-1)
p[j]=∗⟶f(i,j)=f(i,j−2)∥f(i−1,j)&&match(i,j−1)
其中match(i, j) 表示 s[i] 和 p[j] 匹配,即
s
[
i
]
=
=
p
[
j
]
∣
p
[
j
]
=
=
"
s[i] == p[j] | p[j] == "
s[i]==p[j]∣p[j]=="
题解
class Solution { public boolean isMatch(String s, String p) { //f[i][j] 表示 s的前i个字符与 p中的前j个字符是否能够匹配 int n = s.length(); int m = p.length(); boolean[][] f = new boolean[n + 1][m + 1]; f[0][0] = true; for(int i = 0; i < n + 1; i++){ for(int j = 1; j < m + 1; j++){ if(p.charAt(j - 1) != '*'){ f[i][j] = i >= 1 && j >= 1 && f[i - 1][j - 1] && match(s, p, i, j) ; }else{ //* 匹配零个 boolean mathZero = j >= 2 && f[i][j - 2]; //* 匹配多个 boolean matchMany = i >= 1 && j >= 1 && f[i - 1][j] && match(s, p, i, j - 1); f[i][j] = mathZero || matchMany; } } } return f[n][m]; } private boolean match(String s, String p, int i, int j){ return s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.'; } }
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
解析
使用首尾双指针,具体过程如下:
代码
class Solution { public int maxArea(int[] height) { int ans = 0; int l = 0; int r = height.length - 1; while(l < r){ //对应的容器的容量 int temp = (r - l) * Math.min(height[r], height[l]); ans = Math.max(ans, temp); //规则:移动短板 if(height[l] > height[r]){ r--; }else{ l++; } } return ans; } }
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
解析
先对数组排序,这样方便之后处理。整体思路为,通过遍历数组选取第一个数字,在遍历的同时用前后双指针寻
找满足条件的数字即可。
i去重,判断num[i] == num[i -1]
left去重:判断num[left] == num[left + 1]
right去重: 判断num[right] == num[right - 1]
题解
class Solution { public List<List<Integer>> threeSum(int[] nums) { //排序方便去重 Arrays.sort(nums); List<List<Integer>> res = new ArrayList<List<Integer>>(); for(int i = 0; i < nums.length; i++){ if(i > 0 && nums[i] == nums[i - 1]) continue; //i去重 int l = i + 1; int r = nums.length - 1; while(l < r){ int target = 0 - nums[i]; if(nums[l] + nums[r] == target){ List<Integer> list = new ArrayList<Integer>(); list.add(nums[i]); list.add(nums[l]); list.add(nums[r]); res.add(list); while(l < r && l + 1 < nums.length && nums[l] == nums[l + 1]) l++;//l去重 while(l < r && r - 1 >= 0 && nums[r] == nums[r - 1]) r--;//r去重 l++; r--; }else if(nums[l] + nums[r] < target){ l++; }else{ r--; } } } return res; } }
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
示例 2:
输入:digits = “”
输出:[]
示例 3:
输入:digits = “2”
输出:[“a”,“b”,“c”]
解析
首先存储每个数字对应的所有可能的字母,然后进行回溯操作。回溯过程中维护一个字符串,表示已有的字母排列,并记录当前回溯位置。每次尝试对应位置数字的所有字母,即可得到完整排列。
代码
class Solution { List<String> res = new ArrayList<>(); StringBuilder sb = null; String[] map = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; public List<String> letterCombinations(String digits) { if(digits == null || digits.length() == 0) return res; sb = new StringBuilder(); dfs(digits, 0, sb); return res; } private void dfs(String digits, int index, StringBuilder sb){ if(sb.length() == digits.length()){ res.add(new String(sb)); return; } String s = map[digits.charAt(index) - '0']; for(int i = 0; i < s.length(); i++){ sb.append(s.charAt(i)); dfs(digits, index + 1, sb); //回溯 sb.deleteCharAt(sb.length() - 1); } } }
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
解析
快慢双指针,令快指针先走 n 步,之后两指针再同步移动,直到快指针移动到链表结尾,此时慢指针指向的下一个位置即为要删除元素。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode dummy = new ListNode(); dummy.next = head; ListNode fast = head; for(int i = 0; i < n; i++){ fast = fast.next; } ListNode slow = dummy; while(fast != null){ fast = fast.next; slow = slow.next; } //走到了要删的前一个位置 slow.next = slow.next.next; return dummy.next; } }
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = “()”
输出:true
示例 2:
输入:s = “()[]{}”
输出:true
示例 3:
输入:s = “(]”
输出:false
解析
遍历字符串,每遇到一个左括号时,将与其匹配的右括号入栈;当遇到右括号时,与栈顶元素比较配对,不一致则匹配失败。
代码
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for(int i = 0; i < s.length(); i++){
if(s.charAt(i) == '(') stack.push(')');
else if(s.charAt(i) == '[') stack.push(']');
else if(s.charAt(i) == '{') stack.push('}');
else if(stack.isEmpty() || stack.pop() != s.charAt(i)) return false;
}
return stack.isEmpty();
}
}
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
解析
同时遍历两个链表,从中选取较小的元素添加到结果链表,直至两个链表都遍历完成。归并排序的思想
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode mergeTwoLists(ListNode list1, ListNode list2) { ListNode dummy = new ListNode(); ListNode help = dummy; while(list1 != null && list2 != null){ if(list1.val < list2.val){ help.next = list1; list1 = list1.next; }else{ help.next = list2; list2 = list2.next; } help = help.next; } if(list1 != null) help.next = list1; if(list2 != null) help.next = list2; return dummy.next; } }
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
示例 2:
输入:n = 1
输出:[“()”]
解析
一个合法的括号序列需要满足以下两个条件:
因此只要在回溯的同时,记录当前状态已使用的左右括号数,根据使用情况决定下一步状态即可。
代码
class Solution { StringBuilder sb = null; List<String> res = null; public List<String> generateParenthesis(int n) { sb = new StringBuilder(); res = new ArrayList<String>(); dfs(0, 0, n); return res; } private void dfs(int left, int right, int n){ if(left == n && right == n){ res.add(new String(sb)); return; } if(left < n){ sb.append("("); dfs(left + 1, right, n); sb.deleteCharAt(sb.length() - 1); } if(right < n && right < left){ sb.append(")"); dfs(left, right + 1, n); sb.deleteCharAt(sb.length() - 1); } } }
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
解析
归并算法
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode mergeKLists(ListNode[] lists) { if(lists == null || lists.length == 0) return null; return merger(lists, 0, lists.length - 1); } private ListNode merger(ListNode[] lists, int l, int r){ if(l == r) return lists[l]; int mid = l + (r - l) / 2; ListNode left = merger(lists, l, mid); ListNode right = merger(lists, mid + 1, r); return mergerTwoList(left, right); } private ListNode mergerTwoList(ListNode left, ListNode right){ if(left == null) return right; if(right == null) return left; ListNode dummy = new ListNode(); ListNode help = dummy; while(left != null && right != null){ if(left.val < right.val){ help.next = left; left = left.next; }else{ help.next = right; right = right.next; } help = help.next; } if(left != null) help.next = left; if(right != null) help.next = right; return dummy.next; } }
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:
输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:
输入:nums = [1,1,5]
输出:[1,5,1]
解析
从后向前找到第一个升序数 k - 1;
从后向前找到第一个比 k - 1 大的数 t;
交换 k - 1和 t,并将原 k 位置后面的数字升序。
代码
class Solution { public void nextPermutation(int[] nums) { int k = nums.length - 1; //从后往前找到第一个升序数 k - 1 while(k >= 1 && nums[k - 1] >= nums[k]) k--; //没有升序全是降序 if(k == 0) Arrays.sort(nums); else{ //从后往前找第一个大于k - 1的升序数 int t = nums.length - 1; while(nums[t] <= nums[k - 1]) t--; //交换,然后k之后的都是降序 int s = nums[t]; nums[t] = nums[k - 1]; nums[k - 1] = s; //k之后的变为升序 Arrays.sort(nums, k, nums.length); } } }
给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = “(()”
输出:2
解释:最长有效括号子串是 “()”
示例 2:
输入:s = “)()())”
输出:4
解释:最长有效括号子串是 “()()”
示例 3:
输入:s = “”
输出:0
解析
一个合法的括号序列需要满足以下两个条件:
因此可以根据首次不合法的右括号(右括号数量首次大于左括号数量的位置)将原字符串划分成多段,可以看出,最长有效括号一定在段内产生;之后在每一段内找到所有合法括号序列,求出最大值即可。具体算法如下:
代码
class Solution { public int longestValidParentheses(String s) { int res = 0; int start = -1; //起始位置 Stack<Integer> stack = new Stack<>(); for(int i = 0; i < s.length(); i++){ if(s.charAt(i) == '('){ stack.push(i);//下标进栈 }else{ if(stack.isEmpty()){ start = i;//进去的是),更新起始位置 }else{ stack.pop();//弹出( //栈为空和不为空,更新结果 if(!stack.isEmpty()){ res = Math.max(res, i - stack.peek()); }else{ res = Math.max(res, i - start); } } } } return res; } }
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:
输入:nums = [1], target = 0
输出:-1
解析
对于旋转数组,我们无法直接根据 nums[mid] 与 target 的大小关系来判断 target 是在 mid 的左边还是右边,因此需要「分段讨论」。
通过二分找到数组旋转点;
确定 target 在哪个区间;
子区间内二分查找。
代码
情况一
如果搜索区间[left, right]中一定有目标值索引,那么循环截止条件是while(left < right)
情况二
如果搜索区间[left, right]中不一定有目标值索引,那么循环截止条件是while(left <= right);(一般用于搜索区间内是否有某个值)
class Solution { public int search(int[] nums, int target) { int l = 0; int r = nums.length - 1; while(l <= r){ int mid = l + (r - l) / 2; if(nums[mid] == target) return mid; // l - mid 是有序数组, mid + 1 - r 是旋转数组(不有序) if(nums[l] <= nums[mid]){ //判断是不是在l - mid有序数组中 if(target >= nums[l] && target < nums[mid]) r = mid - 1; else l = mid + 1; }else{ // l - mid 是旋转数组(不有序), mid + 1 - r 是有序数组 //判断是不是在mid + 1 - r有序数组中 if(target <= nums[r] && target > nums[mid]) l = mid + 1; else r = mid - 1; } } return -1; } }
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
解析
求左右边界的二分查找。
代码
拿[1,3,3,3,4],target = 3举例,第一次二分mid = 2,但是nums[2]的3并不是最左边界的3,然后leftOrRight为true,表示查找左侧边界的,所以要缩小右边界,right需要往左移,也就是right = mid-1,false 则相反,要查找右侧边界,即扩大左边界
class Solution { public int[] searchRange(int[] nums, int target) { int[] res = new int[]{-1, -1}; res[0] = bsearch(nums, target, true); res[1] = bsearch(nums, target, false); return res; } private int bsearch(int[] nums, int target, boolean leftOrRight){ int res = -1; int l = 0, r = nums.length - 1; while(l <= r){ int mid = l + (r - l) / 2; if(target > nums[mid]) l = mid + 1; else if(target < nums[mid]) r = mid - 1; else{ res = mid; if(leftOrRight) r = mid - 1; else l = mid + 1; } } return res; } }
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例 3:
输入: candidates = [2], target = 1
输出: []
解析
依据每个位置的元素可选取的次数进行搜索。
代码
可以重复选择,所有 i = index, i 每次传递时不变
class Solution { List<List<Integer>> res = new ArrayList<>(); List<Integer> pre = new ArrayList<>(); public List<List<Integer>> combinationSum(int[] candidates, int target) { dfs(candidates, target, 0); return res; } private void dfs(int[] candidates, int target, int index) { //等于零说明结果符合要求 if (target == 0) { res.add(new ArrayList<>(pre)); return; } //遍历,index为本分支上一节点的减数的下标 for (int i = index; i < candidates.length; i++) { //如果减数大于目标值,则差为负数,不符合结果 if (candidates[i] <= target) { pre.add(candidates[i]); //目标值减去元素值 dfs(candidates, target - candidates[i], i); //每次回溯将最后一次加入的元素删除 pre.remove(pre.size() - 1); } } } }
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5]
输出:9
解析
木桶原理,从当前节点往左找最高的高度,往右找最高的高度,这两个高度我们可以看做是木桶的两个木板,能接的雨水由最短的那块决定,累加每个位置能存的雨水量即可。
代码
class Solution { public int trap(int[] height) { int res = 0, l = 0, r = height.length - 1; int lmax = 0, rmax = 0; while(l < r){ //左右两侧最大高度 lmax = Math.max(lmax, height[l]); rmax = Math.max(rmax, height[r]); //移动短板 if(lmax <= rmax){ res = res + lmax - height[l];//短侧接雨水 l++; }else{ res = res + rmax - height[r]; r--; } } return res; } }
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
解析
回溯法,维护两个数组,分别记录当前排列和每个数字的使用情况,之后枚举每个位置可能出现的数字即可。
代码
class Solution { List<List<Integer>> res = new ArrayList<>(); List<Integer> temp = new ArrayList<>(); boolean[] isused; public List<List<Integer>> permute(int[] nums) { isused = new boolean[nums.length]; dfs(nums, 0); return res; } private void dfs(int[] nums, int index){ if(temp.size() == nums.length){ res.add(new ArrayList(temp)); } for(int i = 0; i < nums.length; i++){ if(isused[i] == true) continue; temp.add(nums[i]); isused[i] = true; dfs(nums, i + 1); temp.remove(temp.size() - 1); isused[i] = false; } } }
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
解析
通过两次翻转,完成顺时针旋转,分别是按主对角线翻转,然后再左右翻转即可。
代码
class Solution { public void rotate(int[][] matrix) { //对角线翻转 for(int i = 0; i < matrix.length; i++){ for(int j = 0; j < i; j++){ int temp = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i]= temp; } } //左右对称翻转 for(int i = 0; i < matrix.length; i++){ for(int j = 0; j < matrix.length / 2; j++){ int temp = matrix[i][j]; matrix[i][j] = matrix[i][matrix.length - j - 1]; matrix[i][matrix.length - j - 1]= temp; } } } }
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。
示例 1:
输入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出: [[“bat”],[“nat”,“tan”],[“ate”,“eat”,“tea”]]
示例 2:
输入: strs = [“”]
输出: [[“”]]
示例 3:
输入: strs = [“a”]
输出: [[“a”]]
解析
将每个字符串排序后的结果作为 key,对原字符串数组进行分组,最后提取分组结果即可。
代码
class Solution { public List<List<String>> groupAnagrams(String[] strs) { Map<String, List<String>> map = new HashMap<>(); for(int i = 0; i < strs.length; i++){ //对字符串排序 char[] ch = strs[i].toCharArray(); Arrays.sort(ch); String key = new String(ch); //相同的字符串进行添加 List<String> list = map.getOrDefault(key, new ArrayList<String>()); list.add(strs[i]); //放入map中 map.put(key, list); } return new ArrayList<List<String>>(map.values()); } }
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
解析
前缀和(用于解决区间求和问题) + 动态规划
状态方程
f
(
i
)
=
m
a
x
(
f
(
i
−
1
)
+
n
u
m
s
[
i
]
,
n
u
m
s
[
i
]
)
f(i)=max(f(i−1)+nums[i],nums[i] )
f(i)=max(f(i−1)+nums[i],nums[i])
代码
class Solution {
public int maxSubArray(int[] nums) {
int res = nums[0], pre = nums[0];
for(int i = 1; i < nums.length; i++){
pre = Math.max(pre + nums[i], nums[i]);
res = Math.max(res, pre);
}
return res;
}
}
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
解析
遍历数组的同时维护一个当时能跳到的最远位置,若不能到达此时下标,则说明不能到达最后一个下标,直接返回即可。
代码
class Solution {
public boolean canJump(int[] nums) {
int max = 0; // 能跳的最远距离
for(int i = 0; i < nums.length; i++){
if(i > max) return false;
max = Math.max(max, nums[i] + i);
}
return true;
}
}
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
解析
代码
class Solution { public int[][] merge(int[][] intervals) { //按照start进行排序 Arrays.sort(intervals, (o1, o2) -> {return o1[0] - o2[0];}); List<int[]> res = new ArrayList<>(); res.add(intervals[0]); int index = 0;//res中最后一个序号 for(int i = 1; i < intervals.length; i++){ int[] cur = intervals[i]; int[] cmp = res.get(index); if(cmp[1] >= cur[0]) cmp[1] = Math.max(cmp[1], cur[1]); else{ res.add(cur); index++; } } int[][] ans = new int[res.size()][]; for(int i = 0; i < res.size(); i++){ ans[i] = res.get(i); } return ans; } }
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7
输出:28
示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
解析
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 是到达 i, j 最多路径 ,
从上面移动下来的
f
(
i
,
j
)
=
f
(
i
−
1
,
j
)
f(i, j) = f(i - 1, j)
f(i,j)=f(i−1,j)
从左面移动过来的
f
(
i
,
j
)
=
f
(
i
,
j
−
1
)
f(i, j) = f(i , j - 1)
f(i,j)=f(i,j−1)
f
(
i
,
j
)
=
f
(
i
−
1
,
j
)
+
f
(
i
,
j
−
1
)
f(i, j) = f(i - 1,j) + f(i, j -1)
f(i,j)=f(i−1,j)+f(i,j−1)
代码
class Solution { public int uniquePaths(int m, int n) { int[][] dp = new int[m][n]; for(int i = 0; i < m; i++){ dp[i][0] = 1;//第 0 列 只能向下走,一条路径 } for(int j = 0; j < n; j++){ dp[0][j] = 1;//第 0 列 只能向右走,一条路径 } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; } } return dp[m - 1][n - 1]; } }
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例 1:
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
示例 2:
输入:grid = [[1,2,3],[4,5,6]]
输出:12
解析
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]是到达 i, j 最小路径和 ,
从上面移动下来的
f
(
i
,
j
)
=
f
(
i
−
1
,
j
)
+
o
[
i
]
[
j
]
f(i, j) = f(i - 1, j) + o[i][j]
f(i,j)=f(i−1,j)+o[i][j]
从左面移动过来的
f
(
i
,
j
)
=
f
(
i
,
j
−
1
)
+
o
[
i
]
[
j
]
f(i, j) = f(i , j - 1) + o[i][j]
f(i,j)=f(i,j−1)+o[i][j]
f
(
i
,
j
)
=
m
i
n
(
f
(
i
−
1
,
j
)
+
f
(
i
,
j
−
1
)
)
+
o
[
i
]
[
j
]
f(i, j) = min( f(i - 1,j) + f(i, j -1)) + o[i][j]
f(i,j)=min(f(i−1,j)+f(i,j−1))+o[i][j]
代码
class Solution { public int minPathSum(int[][] grid) { int r = grid.length, c = grid[0].length; int[][] dp = new int[r][c]; dp[0][0] = grid[0][0]; for(int i = 1; i < r; i++){ dp[i][0] = dp[i - 1][0] + grid[i][0]; // 第 0 列只能往下走 } for(int j = 1; j < c; j++){ dp[0][j] = dp[0][j - 1] + grid[0][j]; //第 0 行只能往左走 } for(int i = 1; i < r; i++){ for(int j = 1; j < c; j++){ dp[i][j] = grid[i][j] + Math.min(dp[i - 1][j], dp[i][j - 1]); } } return dp[r - 1][c - 1]; } }
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
3. 1 阶 + 1 阶 + 1 阶
4. 1 阶 + 2 阶
5. 2 阶 + 1 阶
解析
1.定义状态:上一个 n 级的台阶总共有多少种爬法
2.编写状态转移方程:f(n)=f(n-1)+f(n-2)
3.设置初始值:f(0)=1,f(1)=1 到达0台阶的方法有一种,就是不爬
代码
class Solution {
public int climbStairs(int n) {
if(n == 1) return 1;
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i < n + 1; i++){
dp[i] = dp[i - 1] + dp[i -2];
}
return dp[n];
}
}
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
示例 1:
输入:word1 = “horse”, word2 = “ros”
输出:3
解释:
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)
示例 2:
输入:word1 = “intention”, word2 = “execution”
输出:5
解释:
intention -> inention (删除 ‘t’)
inention -> enention (将 ‘i’ 替换为 ‘e’)
enention -> exention (将 ‘n’ 替换为 ‘x’)
exention -> exection (将 ‘n’ 替换为 ‘c’)
exection -> execution (插入 ‘u’)
解析
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 str1 的前 i 个字符和 str2 的前 j 个字符的编辑距离。否则就比较插入、删除和替换三种操作哪种的代价小:
如果 str1 的前 i - 1 个字符和 str2 的前 j 个字符相等,那么我们只需要在 str1 最后插入
一个字符就可以转化为 str2 。
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
+
1
dp[i][j] = dp[i - 1][j] + 1
dp[i][j]=dp[i−1][j]+1
如果 str1 的前 i 个字符和 str2 的前 j - 1 个字符相等,那么我们只需要在 str1 最后删除
一个字符就可以转化为 str2 。
d
p
[
i
]
[
j
]
=
d
p
[
i
]
[
j
−
1
]
+
1
dp[i][j] = dp[i][j - 1] + 1
dp[i][j]=dp[i][j−1]+1
如果 str1 的前 i - 1 个字符和 str2 的前 j - 1 个字符相等,那么我们要判断 str1 和 str2 最后一个字符是否相等:
如果相等,则不需要任何操作。 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] dp[i][j] = dp[i - 1][j - 1] dp[i][j]=dp[i−1][j−1]
如果不相等,则只需要将 str1 最后一个字符修改
为 str2 最后一个字符即可。
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
−
1
]
+
1
dp[i][j] = dp[i - 1][j - 1] + 1
dp[i][j]=dp[i−1][j−1]+1
代码
class Solution { public int minDistance(String word1, String word2) { int m = word1.length(); int n = word2.length(); int[][] dp = new int[m + 1][n + 1]; dp[0][0] = 0; for(int i = 1; i < n + 1; i++){ dp[0][i] = dp[0][i - 1] + 1; } for(int j = 1; j < m + 1; j++){ dp[j][0] = dp[j - 1][0] + 1; } for(int i = 1; i < m + 1; i++){ for(int j = 1; j < n + 1; j++){ if(word1.charAt(i - 1) == word2.charAt(j - 1)){ dp[i][j] = dp[i-1][j-1]; }else{ dp[i][j] = Math.min(dp[i-1][j-1], Math.min(dp[i-1][j], dp[i][j-1])) + 1; } } } return dp[m][n]; } }
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
解析
荷兰国旗问题
代码
class Solution { public void sortColors(int[] nums) { int less = -1, more = nums.length, index = 0; while(index < more){ if(nums[index] < 1){ swap(nums, index++, ++less); }else if(nums[index] > 1){ swap(nums, index, --more); }else{ index++; } } } private void swap(int[] nums, int a, int b){ int temp = nums[a]; nums[a] = nums[b]; nums[b] = temp; } }
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
解释:最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。
示例 2:
输入:s = “a”, t = “a”
输出:“a”
解释:整个字符串 s 是最小覆盖子串。
示例 3:
输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
解析
使用双指针 i、j,其中 i 遍历字符串 s,j 用来寻找满足条件的位置,使得 j 到 i 的字符串刚好包含字符串 t 的所有字符。
双指针一个left 一个 right,中间夹着的就是子串,right不停往右走,一旦之间的子串符合要求,left收缩直到长度最小,当子串不符合要求的时候,right 继续往右走。
代码
class Solution { public String minWindow(String s, String t) { int[] map = new int[128]; //ASCII码的map for(int i = 0; i < t.length(); i++){ map[t.charAt(i)]++; } String res = null; int count = 0;//统计是否全部包含t //i 是 快 j 是慢指针 for(int i = 0, j = 0; i < s.length(); i++){ //当前字符 map[s.charAt(i)]--; if(map[s.charAt(i)] >= 0){ count++; } //慢指针有冗余字符 while(j <= i && map[s.charAt(j)] < 0) map[s.charAt(j++)]++; if(count == t.length()){ if(res == null || res.length() > i - j + 1){ res = s.substring(j, i + 1); } } } return res == null ? "" : res; } }
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
解析
枚举每个元素是否选择,遍历到最后一个元素结束搜索。
代码
class Solution { List<List<Integer>> res = new ArrayList<>(); List<Integer> temp = new ArrayList<>(); public List<List<Integer>> subsets(int[] nums) { dfs(nums, 0); return res; } private void dfs(int[] nums, int index){ if(index == nums.length){ res.add(new ArrayList(temp)); return; } //选择当前元素 temp.add(nums[index]); dfs(nums, index + 1); //回溯 temp.remove(temp.size() - 1); //不选择当前元素 dfs(nums, index + 1); } }
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true
解析
参考 岛屿的数量
枚举矩阵的每个位置作为单词的起点,只要能找到对应单词就直接返回 true。
具体在每次搜索中,可以依次尝试相邻未访问格子的字母,只要能和单词对应位置匹配,就继续向下搜索。
代码
class Solution { public boolean exist(char[][] board, String word) { for(int i = 0; i < board.length; i++){ for(int j = 0; j < board[0].length; j++){ if(dfs(board, word, i, j, 0)) return true; } } return false; } public boolean dfs(char[][] board, String word, int i, int j, int index){ if(index == word.length()) return true; if(i < 0 || i >= board.length || j < 0 || j >= board[0].length //边界条件 || board[i][j] != word.charAt(index)) return false; char tmp = board[i][j]; board[i][j] = '.'; boolean t1 = dfs(board, word, i - 1, j, index + 1); boolean t2 = dfs(board, word, i + 1, j, index + 1); boolean t3 = dfs(board, word, i, j - 1, index + 1); boolean t4 = dfs(board, word, i, j + 1, index + 1); board[i][j] = tmp; return t1 || t2 || t3 || t4; } }
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10
解析
本题与 0042. 接雨水 解法相似,但由于计算面积需要找到当前柱子左右第一个比它小的柱子,因此栈内顺序与「接雨水」相反,即:从栈顶到栈底的高度右大到小,这样就可以得到当前高度的左右边界,遍历所有高度即可求出最大值。
代码
class Solution { public int largestRectangleArea(int[] heights) { Deque<Integer> stack = new ArrayDeque<>();//单调栈 int res = 0; int[] newHeights = new int[heights.length + 1]; for(int i = 0; i < heights.length; i++){ newHeights[i] = heights[i]; } newHeights[heights.length] = -1;//为了清空栈 for(int i = 0; i < newHeights.length; i++){ while(!stack.isEmpty() && newHeights[i] < newHeights[stack.peek()]){ int index = stack.pop(); int left = stack.isEmpty() ? -1 : stack.peek(); res = Math.max(res, (i - left - 1) * newHeights[index]); } stack.push(i); } return res; } }
给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
示例 1:
输入:matrix = [[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“1”,“1”,“1”,“1”],[“1”,“0”,“0”,“1”,“0”]]
输出:6
解释:最大矩形如上图所示。
示例 2:
输入:matrix = []
输出:0
解析
本题是 0084. 柱状图中最大的矩形 的改编版,只需在原有基础上对每一行作为 x 轴的柱状图求解即可。
代码
class Solution { public int maximalRectangle(char[][] matrix) { int r = matrix.length, c = matrix[0].length; int[] height = new int[c]; int res = 0; for(int i = 0; i < r; i++){ //对每一行统计柱状图 for(int j = 0; j < c; j++){ if(matrix[i][j] == '1') height[j]++; else height[j] = 0; } res = Math.max(res, maxArea(height)); } return res; } private int maxArea(int[] height){ Deque<Integer> stack = new ArrayDeque<>(); int[] newHeight = new int[height.length + 1]; for(int i = 0; i < height.length; i++){ newHeight[i] = height[i]; } newHeight[height.length] = -1;//为了清空栈 int res = 0; for(int i = 0; i < newHeight.length; i++){ while(!stack.isEmpty() && newHeight[i] < newHeight[stack.peek()]){ int idx = stack.pop(); int left = stack.isEmpty() ? -1: stack.peek(); res = Math.max(res, (i - left - 1) * newHeight[idx]); } stack.push(i); } return res; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。