文章目錄
- 32、最長有效括號(棧)
- 33、搜索旋轉排序數組(二分查找)
- 34、在排序數組中查找元素的第一個和最後一個位置(二分查找)
- 35、搜索插入位置(二分查找)
- 36、有效的數獨(HashSet)
- 37、解數獨(遞歸回溯算法)
- 39、組合總和I(遞歸回溯)
- 40、組合總數II(遞歸回溯)
- 42、接雨水(雙指針!)
- 43、字符串相乘(雙指針)
- 44、 通配符匹配(雙指針!)
- 55、跳躍遊戲I(貪心算法)
- 45、跳躍遊戲II(貪心算法)
- 46、全排列I(遞歸回溯算法)
- 47、全排列II(遞歸回溯算法)
- 48、旋轉圖像(二維數組)
- 49、字母異位詞分組(hash)
- 50、Pow(x, n)(分治算法)
- 53、最長子序和(動態規劃!)
- 54、螺旋矩陣(二維數組)
- 55、跳躍遊戲(貪心算法)
- 56、合併區間(重要!!)
- 57、插入區間(重要!!)
- 58、最後一個單詞的長度
- 59、螺旋矩陣II(二維數組)
32、最長有效括號(棧)
**題目:**給定一個只包含 ‘(’ 和 ‘)’ 的字符串,找出最長的包含有效括號的子串的長度。
示例 1:
輸入: "(()"
輸出: 2
解釋: 最長有效括號子串爲 "()"
示例 2:
輸入: ")()())"
輸出: 4
解釋: 最長有效括號子串爲 "()()"
代碼:
/**
time:O(n)
space:O(n)
*/
public class Solution {
public int longestValidParentheses(String s) {
Stack<Integer> stack = new Stack<>();
int res = 0;
int start = -1;
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-start);
}else{
//如果彈出後棧中還有左括號,計算有效括號的長度
res = Math.max(res,i-stack.peek());
}
}
}
}
return res;
}
}
33、搜索旋轉排序數組(二分查找)
**題目:**假設按照升序排序的數組在預先未知的某個點上進行了旋轉。
( 例如,數組 [0,1,2,4,5,6,7] 可能變爲 [4,5,6,7,0,1,2] )。
搜索一個給定的目標值,如果數組中存在這個目標值,則返回它的索引,否則返回 -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
代碼:
/**
time:O(logn)
space:O(1)
*/
public class Solution {
public int search(int[] nums, int target) {
//判斷邊界條件
if(nums.length==0 || nums==null){
return -1;
}
//使用二分查找法
int start = 0;
int end = nums.length-1;
while (start+1<end){
int mid = (end-start)/2+start;
if(nums[mid]==target) return mid;
//如果前半段爲遞增的
if(nums[start]<=nums[mid]){
//判斷target的位置
if(nums[start]<=target && target<=nums[mid]){
end = mid;
//如果上述條件不滿足
}else{
start = mid;
}
//如果後半段爲遞增的
}else{
//判斷target的位置
if(nums[mid]<=target && target<=nums[end]){
start=mid;
//如果上述條件不滿足
}else{
end=mid;
}
}
}
if(nums[start]==target) return start;
if(nums[end]==target) return end;
return -1;
}
}
34、在排序數組中查找元素的第一個和最後一個位置(二分查找)
**題目:**給定一個按照升序排列的整數數組 nums,和一個目標值 target。找出給定目標值在數組中的開始位置和結束位置。你的算法時間複雜度必須是 O(log n) 級別。如果數組中不存在目標值,返回 [-1, -1]。
示例 1:
輸入: nums = [5,7,7,8,8,10], target = 8
輸出: [3,4]
示例 2:
輸入: nums = [5,7,7,8,8,10], target = 6
輸出: [-1,-1]
代碼:
/**
* time:O(n)
* space:O(1)
*/
public class Solution {
public int[] searchRange(int[] nums, int target) {
//二分查找算法
if(nums.length==0 || nums==null) return new int[]{-1,-1};
//找出初始索引
int start = findFirst(nums, target);
if(start==-1) return new int[]{-1,-1};
//找出最後索引
int end = findLast(nums, target);
return new int[]{start,end};
}
private int findFirst(int[] nums, int target) {
//二分查找
int start = 0;
int end = nums.length-1;
while (start+1<end){
//二分的mid
int mid = (end-start)/2+start;
//如果target在mid的右邊
if(target>nums[mid]){
start = mid;
}else{
end = mid;
}
}
//先返回start,再返回end
if(nums[start]==target) return start;
if(nums[end]==target) return end;
return -1;
}
private int findLast(int[] nums, int target) {
int start = 0;
int end = nums.length-1;
while (start+1<end){
int mid = (end-start)/2+start;
//如果target在nums的左邊
if(target<nums[mid]){
end = mid;
}else{
start = mid;
}
}
//先返回end,再返回start
if(nums[end]==target) return end;
if(nums[start]==target) return start;
return -1;
}
}
35、搜索插入位置(二分查找)
**題目:**給定一個排序數組和一個目標值,在數組中找到目標值,並返回其索引。如果目標值不存在於數組中,返回它將會被按順序插入的位置。你可以假設數組中無重複元素。
示例 1:
輸入: [1,3,5,6], 5
輸出: 2
示例 2:
輸入: [1,3,5,6], 2
輸出: 1
示例 3:
輸入: [1,3,5,6], 7
輸出: 4
示例 4:
輸入: [1,3,5,6], 0
輸出: 0
代碼:
/**
* time:O(logn)
* space:O(1)
*/
public class Solution {
public int searchInsert(int[] nums, int target) {
//二分查找
int start = 0;
int end = nums.length-1;
while (start+1<end){
int mid = (end-start)/2+start;
if(nums[mid]==target) return mid;
else if(target<nums[mid]) end = mid;
else start = mid;
}
//如果數組中不存在target,就判斷target和nums[start]與nums[end]的大小,進而插入到相應位置
if(target<=nums[start]){//1、target<=nums[start]
return start;
}else if(target<=nums[end]){//2、nums[start]<target<=nums[end]
return end;
}else { //3、target>nums[end]
return end+1;
}
}
}
36、有效的數獨(HashSet)
**題目:**判斷一個 9x9 的數獨是否有效。只需要根據以下規則,驗證已經填入的數字是否有效即可。
數字 1-9 在每一行只能出現一次。
數字 1-9 在每一列只能出現一次。
數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。
上圖是一個部分填充的有效的數獨。
數獨部分空格內已填入了數字,空白格用 ‘.’ 表示。
示例 1:
輸入:
[
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
輸出: true
示例 2:
輸入:
[
["8","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
輸出: false
解釋: 除了第一行的第一個數字從 5 改爲 8 以外,空格內其他數字均與 示例1 相同。
但由於位於左上角的 3x3 宮內有兩個 8 存在, 因此這個數獨是無效的。
說明:
一個有效的數獨(部分已被填充)不一定是可解的。
只需要根據以上規則,驗證已經填入的數字是否有效即可。
給定數獨序列只包含數字 1-9 和字符 '.' 。
給定數獨永遠是 9x9 形式的。
思路:
把這個數獨當成一個二維數組來遍歷,利用HashSet取出重複元素
如何取出每三宮格的重複元素?順序是從左到右進行每一個三宮格的去重:
第一個3宮格: 第二個3宮格: 第三個3宮格:
(0,0) (0,1) (0,2) (0,3) (0,4) (0,5) (0,6) (0,7) (0,8)
(1,0) (1,1) (1,2) (1,3) (1,4) (1,5) (1,6) (1,7) (1,8)
(2,0) (2,1) (2,2) (2,3) (2,4) (2,5) (2,6) (2,7) (2,8)
代碼:
/**
* time:o(n^2)
* space:O(n)
*/
class Solution {
public boolean isValidSudoku(char[][] board) {
//遍歷這個二維數組
for(int i=0;i<board.length;i++){
//去除每一行的重複數據
HashSet<Character> rows = new HashSet<>();
//去除每一列的重複數據
HashSet<Character> cols = new HashSet<>();
//去除每個三宮格的重複數據
HashSet<Character> cub = new HashSet<>();
for(int j=0;j<board[0].length;j++){
//去除每一行的重複數據
if(board[i][j]!='.' && !rows.add(board[i][j])) return false;
//去除每一列的重複數據
if(board[j][i]!='.' && !cols.add(board[j][i])) return false;
//從左到右,去除每個三宮格的重複數據
int rowIndex = (i/3)*3;
int colIndex = (i%3)*3;
if(board[rowIndex+j/3][colIndex+j%3]!='.' && !cub.add(board[rowIndex+j/3][colIndex+j%3])) return false;
}
}
return true;
}
}
37、解數獨(遞歸回溯算法)
**題目:**編寫一個程序,通過已填充的空格來解決數獨問題。
一個數獨的解法需遵循如下規則:
數字 1-9 在每一行只能出現一次。
數字 1-9 在每一列只能出現一次。
數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。
空白格用 ‘.’ 表示。
答案被標成紅色。Note:
給定的數獨序列只包含數字 1-9 和字符 '.' 。
你可以假設給定的數獨只有唯一解。
給定數獨永遠是 9x9 形式的。
思路:
遞歸回溯算法:深度優先搜索,在每個空格內填入數據,使其滿足數獨的條件
代碼:
public class Solution {
public void solveSudoku(char[][] board) {
if(board.length==0 || board==null) return;
solve(board);
}
public boolean solve(char[][] board){
//深度優先搜索,遞歸回溯算法
for(int i=0;i<board.length;i++){
for(int j=0;j<board[0].length;j++){
//說明這個位置需要填入一個數字
if(board[i][j]=='.'){
//填入的數字可能爲1-9中的一個
for(char c='1';c<='9';c++){
//該位置插入c後是否滿足數獨的條件,c=1-9
if(isValid(board,i,j,c)){
board[i][j] = c;
//插入成功後,繼續深度搜索
//判斷下一個位置能否插入成功,如果成功返回true
if(solve(board)) return true;
//如果失敗,返回上一層,變爲初始值
else board[i][j] = '.';
}
}
//這一層循環完畢後,1-9都不能插入,返回false
return false;
}
}
}
return true;
}
private boolean isValid(char[][] board, int row, int col, char c) {
for (int i=0;i<9;i++){
//判斷每一行有沒有和要插入的c重複的
if(board[row][i] != '.' && board[row][i]==c) return false;
//判斷每一列有沒有和要插入的c重複的
if(board[i][col] != '.' && board[i][col]==c) return false;
/**
* 假如說(3,4):row=3,col=4
* (3,3)、 (3,4)、 (3,5)
* (4,3)、 (4,4)、 (4,5)
* (5,3)、 (5,4)、 (5,5)
*/
if(board[(row/3)*3+i/3][(col/3)*3+i%3] !='.' && board[(row/3)*3+i/3][(col/3)*3+i%3]==c) return false;
}
return true;
}
}
39、組合總和I(遞歸回溯)
**題目:**給定一個無重複元素的數組 candidates 和一個目標數 target ,找出 candidates 中所有可以使數字和爲 target 的組合。candidates 中的數字可以無限制重複被選取。
說明:
所有數字(包括 target)都是正整數。
解集不能包含重複的組合。
示例 1:
輸入: candidates = [2,3,6,7], target = 7,
所求解集爲:
[
[7],
[2,2,3]
]
示例 2:
輸入: candidates = [2,3,5], target = 8,
所求解集爲:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
代碼:
/**
* time:O(2^n)
* space:O(n)
*/
public class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
if(candidates.length==0 || candidates==null) return res;
//先對candidates進行排序,便於剪枝
Arrays.sort(candidates);
ArrayList<Integer> list = new ArrayList<>();
dfs(res,candidates,list,target,0);
return res;
}
/**
* @param res 要返回的外層的大的數組
* @param candidates 要遍歷的數組
* @param list 大的數組中的每個小的數組
* @param target 目標值
* @param start 初始值,用來去除已經遍歷過的重複元素
*/
private void dfs(List<List<Integer>> res, int[] candidates, ArrayList<Integer> list,int target, int start) {
//遞歸的終止條件,剪枝
if(target<0) return;
if(target==0){
//說明滿足條件,將小的數組加入到大的數組中
res.add(new ArrayList<>(list));
//剪枝,結束當前循環
return;
}
//如果後面的數比target要大,就不需要遍歷了
for(int i=start;i<candidates.length && target>=candidates[i];i++){
//將當前數加入到數組中
list.add(candidates[i]);
//繼續進行深度優先遍歷
dfs(res,candidates,list,target-candidates[i],i);
//遞歸回溯到上一層之後,要消除上一層新加入的數的影響,加入該層新遍歷的數,繼續深度優先搜索
list.remove(list.size()-1);
}
}
}
40、組合總數II(遞歸回溯)
**題目:**給定一個數組 candidates 和一個目標數 target ,找出 candidates 中所有可以使數字和爲 target 的組合。
candidates 中的每個數字在每個組合中只能使用一次。
說明:
所有數字(包括目標數)都是正整數。
解集不能包含重複的組合。
示例 1:
輸入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集爲:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
輸入: candidates = [2,5,2,1,2], target = 5,
所求解集爲:
[
[1,2,2],
[5]
]
**思路:**本題和上一題基本相同,唯一不同的地方在於candidates 中的每個數字在每個組合中只能使用一次,通知需要去除重複組合
代碼:
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
if(candidates.length==0||candidates==null) return res;
List<Integer> list = new ArrayList<>();
Arrays.sort(candidates);
dfs(res,list,candidates,target,0);
return res;
}
private void dfs(List<List<Integer>> res, List<Integer> list, int[] candidates, int target, int start) {
if(target<0) return;
if(target==0){
res.add(new ArrayList<>(list));
return;
}
for(int i=start;i<candidates.length && target>=candidates[i];i++){
//去重
if(i!=start && candidates[i]==candidates[i-1]) continue;
list.add(candidates[i]);
//candidates 中的每個數字在每個組合中只能使用一次。
dfs(res,list,candidates,target-candidates[i],i+1);
list.remove(list.size()-1);
}
}
}
42、接雨水(雙指針!)
**題目:**給定 n 個非負整數表示每個寬度爲 1 的柱子的高度圖,計算按此排列的柱子,下雨之後能接多少雨水。
上面是由數組 [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
思路:
-
首先我們需要搞清楚,下標爲
i
的雨水量是由什麼決定的.
是由i
左右兩邊最大值中較小的那個減去height[i]
決定的.例[0,1,0,2,1,0,1,3,2,1,2,1]
中,下標爲2
的位置 值爲0,而它的用水量是由左邊的最大值1
,右邊最大值3
中較小的那個 也就是1減去0得到的。 -
本題解的雙指針先找到當前維護的左、右最大值中較小的那個,例當前
i
處左邊的最大值如果比右邊的小,那麼就可以不用考慮i
處右邊最大值的影響了,因爲i
處 右邊真正的最大值絕對比左邊的最大值要大,在不斷遍歷時,更新left_max
和right_max
以及返回值即可。例[0,1,0,2,1,0,1,3,2,1,2,1]
中i=2
時,值爲0,此時left_max
一定爲1,當前right_max
如果爲2,即便right_max
不是真正的i右邊的最大值,也可忽略右邊最大值的影響,因爲右邊真正的最大值一定比左邊真正的最大值大。
代碼:
public class Soluiton {
public int trap(int[] height) {
//使用雙指針解法
int left = 0;
int right = height.length-1;
//這兩個指針用來維護當前列左邊序列和當前列右邊序列中值較小的那個位置
int left_max = 0;
int right_max = 0;
int res = 0;
while(left<=right){
//如果當前序列左邊的最大值比右邊最大值小
if(left_max<right_max){
//以左邊最大值爲準
if(height[left]<left_max) {
res += left_max-height[left];
} else {
left_max = height[left];
}
left++;
}else{
//以右邊最大值爲準
if(height[right]<right_max) {
res += right_max-height[right];
} else {
right_max = height[right];
}
right--;
}
}
return res;
}
}
43、字符串相乘(雙指針)
**題目:**給定兩個以字符串形式表示的非負整數 num1
和 num2
,返回 num1
和 num2
的乘積,它們的乘積也表示爲字符串形式。
示例 1:
輸入: num1 = "2", num2 = "3"
輸出: "6"
示例 2:
輸入: num1 = "123", num2 = "456"
輸出: "56088"
思路:
該算法是通過兩數相乘時,乘數某位與被乘數某位相乘,與產生結果的位置的規律來完成。具體規律如下:
乘數 num1 位數爲 M,被乘數 num2 位數爲 N, num1 x num2 結果 res 最大總位數爲 M+N
num1[i] x num2[j] 的結果爲 tmp(位數爲兩位,"0x","xy"的形式),其第一位位於 res[i+j],第二位位於 res[i+j+1]。
當index1=1,index0=0
時,就是2*4=08
,因此08位於indices[i+j,i+j+8]=indices[1+0,1+0+1]=indices[1,2]
位置。
i=2;j=1
res[i+j] = res[3]=0
res[i+j+1] = res[4]=0
sum = res[i+j+1]+num1[i]*num2[j]=15
res[i+j+1] = res[4]=15%10=5
res[i+j] = res[i+j]+res[3]=0+15/10=1
i=1;j=1
res[i+j] = res[2]=0
res[i+j+1] = res[3]=1
sum = res[i+j+1]+num1[i]*num2[j]=1+10=11
res[i+j+1] = res[3]=11%10=1
res[i+j] = res[2]+res[2]=0+11/10=1
i=0;j=1
res[i+j] = res[1]=0
res[i+j+1] = res[2]=1
sum = res[i+j+1]+num1[i]*num2[j]=1+5=6
res[i+j+1] = res[2]=6%10=6
res[i+j] =res[i+j]+ res[1]=0+6/10=0
經過第一輪計算結果爲0610,後面的依次類推。
代碼:
public class Solution {
public String multiply(String num1, String num2) {
//排除345*0和0*345的情況
if(num1.equals("0") || num2.equals("0")) return "0";
//最後結果的總位數就是兩個字符串長度相加的結果
int[] res = new int[num1.length()+num2.length()];
//倒敘遍歷這兩個字符串,進行相乘
for(int i=num1.length()-1; i>=0; i--){
//將這個字符轉成實際的數
int n1 = num1.charAt(i)-'0';
for(int j=num2.length()-1; j>=0; j--){
int n2 = num2.charAt(j)-'0';
int sum = res[i+j+1]+n1*n2;
res[i+j+1] = sum%10;
res[i+j] += sum/10;
}
}
//最後將計算的結果使用StringBuilder拼接起來
StringBuilder stringBuilder = new StringBuilder();
for(int i=0;i<res.length;i++){
if(i==0 && res[i]==0) continue;
stringBuilder.append(res[i]);
}
return stringBuilder.toString();
}
}
44、 通配符匹配(雙指針!)
**題目:**給定一個字符串 (s) 和一個字符模式 § ,實現一個支持 ‘?’ 和 ‘*’ 的通配符匹配。
‘?’ 可以匹配任何單個字符。
‘*’ 可以匹配任意字符串(包括空字符串)。
兩個字符串完全匹配纔算匹配成功。
說明:
s 可能爲空,且只包含從 a-z 的小寫字母。
p 可能爲空,且只包含從 a-z 的小寫字母,以及字符 ? 和 *。
示例 1:
輸入:
s = "aa"
p = "a"
輸出: false
解釋: "a" 無法匹配 "aa" 整個字符串。
示例 2:
輸入:
s = "aa"
p = "*"
輸出: true
解釋: '*' 可以匹配任意字符串。
示例 3:
輸入:
s = "cb"
p = "?a"
輸出: false
解釋: '?' 可以匹配 'c', 但第二個 'a' 無法匹配 'b'。
示例 4:
輸入:
s = "adceb"
p = "*a*b"
輸出: true
解釋: 第一個 '*' 可以匹配空字符串, 第二個 '*' 可以匹配字符串 "dce".
示例 5:
輸入:
s = "acdcb"
p = "a*c?b"
輸出: false
思路:
本題和正則表達式匹配很相似,這題可以使用動態規劃忽或者雙指針,上題正則表達式匹配值使用的是動態規劃,使用雙指針比較好記和理解。
time:O(m*n)
space:O(1)
代碼:
class Solution {
public boolean isMatch(String s, String p) {
//定義兩個指針來遍歷s,p
int sPositon = 0;
int pPositon = 0;
//定義指針記錄p中*的初始位置pPosition
int star = -1;
//定義指針記錄當p中爲*時,對應的sPosition的位置
int match = 0;
while (sPositon<s.length()){
//情況1:sPosition對應的字符和pPosition對應的字符正好相同,或者pPosition對應的字符爲?
if(pPositon<p.length() && (p.charAt(pPositon)==s.charAt(sPositon) ||p.charAt(pPositon)=='?')){
sPositon++;
pPositon++;
//情況2:pPosition對應的字符爲*,需要更新star和match
}else if(pPositon<p.length() && p.charAt(pPositon)=='*'){
//更新*對應的初始位置
star = pPositon;
//更新*字符對應的s中的字符
match = sPositon;
pPositon++;
}else if(star!=-1){
//讓pPosition處於star後面的一個位置,讓sPotion不斷的向後移動,比較sPotion和pPositon會不會相等
pPositon=star+1;
match++;
sPositon=match;
}else{
return false;
}
}
//情況3:p很可能還沒走到最後的位置
while (pPositon<p.length() ){
//如果沒走完的位置不是*,就無法匹配
if((p.charAt(pPositon)!='*')) return false;
pPositon++;
}
return true;
}
}
55、跳躍遊戲I(貪心算法)
題目:給定一個非負整數數組,你最初位於數組的第一個位置。
數組中的每個元素代表你在該位置可以跳躍的最大長度。
判斷你是否能夠到達最後一個位置。
示例 1:
輸入: [2,3,1,1,4]
輸出: true
解釋: 我們可以先跳 1 步,從位置 0 到達 位置 1, 然後再從位置 1 跳 3 步到達最後一個位置。
示例 2:
輸入: [3,2,1,0,4]
輸出: false
解釋: 無論怎樣,你總會到達索引爲 3 的位置。但該位置的最大跳躍長度是 0 , 所以你永遠不可能到達最後一個位置。
思路:貪心
我們可以用貪心的方法解決這個問題。
設想一下,對於數組中的任意一個位置y
,我們如何判斷它是否可以到達?根據題目的描述,只要存在一個位置 x
,它本身可以到達,並且它跳躍的最大長度爲 x+nums[x]
,這個值大於等於 y
,即 x+nums[x]≥y
,那麼位置 y
也可以到達。
換句話說,對於每一個可以到達的位置x
,它使得x+1,x+2,⋯ ,x+nums[x]
這些連續的位置都可以到達。
這樣以來,我們依次遍歷數組中的每一個位置,並實時維護 最遠可以到達的位置。對於當前遍歷到的位置 x
,如果它在 最遠可以到達的位置 的範圍內,那麼我們就可以從起點通過若干次跳躍到達該位置,因此我們可以用 x+nums[x]
更新最遠可以到達的位置。
在遍歷的過程中,如果 最遠可以到達的位置 大於等於數組中的最後一個位置,那就說明最後一個位置可達,我們就可以直接返回 True 作爲答案。反之,如果在遍歷結束後,最後一個位置仍然不可達,我們就返回 False 作爲答案。
以題目中的示例一
[2, 3, 1, 1, 4]
爲例:
我們一開始在位置 0,可以跳躍的最大長度爲 2,因此最遠可以到達的位置被更新爲 2;
我們遍歷到位置 1,由於 1≤2,因此位置 1 可達。我們用 1 加上它可以跳躍的最大長度 3 ,將最遠可以到達的位置更新爲 4 。由於 4 大於等於最後一個位置 4,因此我們直接返回 True。
我們再來看看題目中的示例二
[3, 2, 1, 0, 5]
我們一開始在位置 000,可以跳躍的最大長度爲 333,因此最遠可以到達的位置被更新爲 333;
我們遍歷到位置 111,由於 1≤3 ,因此位置 1 可達,加上它可以跳躍的最大長度 2 得到 3 ,沒有超過最遠可以到達的位置;
位置 2 、位置 3 同理,最遠可以到達的位置不會被更新;
我們遍歷到位置 4 ,由於 4>3 ,因此位置 4 不可達,我們也就不考慮它可以跳躍的最大長度了。
在遍歷完成之後,位置 4 仍然不可達,因此我們返回 False。
代碼:
/**
time:O(n)
space:O(1)
*/
public class Solution {
public boolean canJump(int[] nums) {
if(nums==null) return false;
//最大可以跳的步數
int max = 0;
for(int i=0;i<nums.length;i++){
if(i>max) return false;
max = Math.max(nums[i]+i,max);
}
return true;
}
}
45、跳躍遊戲II(貪心算法)
**題目:**給定一個非負整數數組,你最初位於數組的第一個位置。
數組中的每個元素代表你在該位置可以跳躍的最大長度。
你的目標是使用最少的跳躍次數到達數組的最後一個位置。
示例:
輸入: [2,3,1,1,4]
輸出: 2
解釋: 跳到最後一個位置的最小跳躍數是 2。
從下標爲 0 跳到下標爲 1 的位置,跳 1 步,然後跳 3 步到達數組的最後一個位置。
代碼:
class Solution {
public int jump(int[] nums) {
int step = 0;
//每一步可以跳躍的數的邊界
int end = 0;
//最大可以跳躍的位置
int maxPosition = 0;
for(int i=0;i<nums.length-1;i++){
//確定最大可以跳躍的位置
maxPosition = Math.max(maxPosition,nums[i]+i);
if(i==end){
end = maxPosition;
step++;
}
}
return step;
}
}
46、全排列I(遞歸回溯算法)
**題目:**給定一個 沒有重複 數字的序列,返回其所有可能的全排列。
示例:
輸入: [1,2,3]
輸出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
思路:
https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-xiang-jie-by-labuladong-2/
1、路徑:也就是已經做出的選擇。
2、選擇列表:也就是你當前可以做的選擇。
3、結束條件:也就是到達決策樹底層,無法再做選擇的條件。
代碼方面,回溯算法的框架:
result = []
def backtrack(路徑, 選擇列表):
if 滿足結束條件:
result.add(路徑)
return
for 選擇 in 選擇列表:
做選擇
backtrack(路徑, 選擇列表)
撤銷選擇
其核心就是 for 循環裏面的遞歸,在遞歸調用之前「做選擇」,在遞歸調用之後「撤銷選擇」
for 選擇 in 選擇列表:
# 做選擇
將該選擇從選擇列表移除
路徑.add(選擇)
backtrack(路徑, 選擇列表)
# 撤銷選擇
路徑.remove(選擇)
將該選擇再加入選擇列表
代碼:
/**
* time:O(n!)
* space:O(n)
*/
class Solution {
//定義一個list存放最終的結果
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
if(nums.length==0 || nums==null){
return null;
}
//定義一個list存放選擇路徑
List<Integer> pathList = new ArrayList<>();
//深度優先遍歷
dfs(nums,pathList);
return res;
}
private void dfs(int[] nums, List<Integer> pathList) {
//如果pathList的長度和nums長度相同,就說找到了一個結果
if(nums.length==pathList.size()){
res.add(new ArrayList<>(pathList));
return;
}
for(int i=0;i<nums.length;i++){
//排除不合法的選擇
if(pathList.contains(nums[i])){
continue;
}
//選擇
pathList.add(nums[i]);
//進行下一層樹的結果的搜索
dfs(nums,pathList);
//撤銷上一次的選擇
pathList.remove(pathList.size()-1);
}
}
}
47、全排列II(遞歸回溯算法)
**題目:**給定一個可包含重複數字的序列,返回所有不重複的全排列。
示例:
輸入: [1,1,2]
輸出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
思路:
路徑:也就是已經做出的選擇。
選擇列表:也就是你當前可以做的選擇。
結束條件:也就是到達決策樹底層,無法再做選擇的條件。
我們所使用的框架基本就是:
LinkedList result = new LinkedList();
public void backtrack(路徑,選擇列表){
if(滿足結束條件){
result.add(結果);
}
for(選擇:選擇列表){
做出選擇;
backtrack(路徑,選擇列表);
撤銷選擇;
}
}
其中最關鍵的點就是:在遞歸之前做選擇,在遞歸之後撤銷選擇。
由於本題需要返回所有不重複的全排列,有限制條件,所以需要進行剪枝。這裏第一步先要給數組進行排序。
首先,先要給nums進行排序,這樣的做目的是方便剪枝
其次,我們已經選擇過的不需要再放進去了
接下來,如果當前節點與他的前一個節點一樣,並其他的前一個節點已經被遍歷過了,那我們也就不需要了。
代碼:
public class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
if(nums.length==0 || nums==null) return null;
//對數組進行排序
Arrays.sort(nums);
List<Integer> pathList = new ArrayList<>();
dfs(pathList,nums,new boolean[nums.length]);
return res;
}
private void dfs(List<Integer> pathList, int[] nums, boolean[] used) {
//說明找到符合條件的結果
if(pathList.size()==nums.length){
res.add(new ArrayList<>(pathList));
return;
}
for(int i=0;i<nums.length;i++){
//已經放進路徑中的數就不要再放進路徑中了
if(used[i]){
continue;
}
//如果當前節點與他的前一個節點一樣,並其他的前一個節點已經被遍歷過了,那我們也就不需要了。
if(i>0 && nums[i]==nums[i-1] && used[i-1]){
break;
}
//選擇
pathList.add(nums[i]);
used[i] = true;
//繼續下一層的遍歷
dfs(pathList, nums, used);
//撤銷選擇
pathList.remove(pathList.size()-1);
used[i] = false;
}
}
}
48、旋轉圖像(二維數組)
**題目:**給定一個 n × n 的二維矩陣表示一個圖像。將圖像順時針旋轉 90 度。
說明:你必須在原地旋轉圖像,這意味着你需要直接修改輸入的二維矩陣。請不要使用另一個矩陣來旋轉圖像。
示例 1:
給定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋轉輸入矩陣,使其變爲:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:
給定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋轉輸入矩陣,使其變爲:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
思路:
給定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
先按照對角線交換位置:
[
[1,4,7],
[2,5,8],
[3,6,9]
]
再按照中間的數軸交換位置:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
代碼:
public class Solution {
public void rotate(int[][] matrix) {
//經過兩次對摺即可
//第一次:沿着右對角線對摺
for(int i=0;i<matrix.length;i++){
for(int j=i;j<matrix[0].length;j++){
int tmp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = tmp;
}
}
//第二次,沿着矩陣的垂直線對摺
for(int i=0;i<matrix.length;i++){
for(int j=0;j<matrix[0].length/2;j++){
int temp = matrix[i][j];
matrix[i][j] = matrix[i][matrix.length-1-j];
matrix[i][matrix.length-1-j] = temp;
}
}
}
}
49、字母異位詞分組(hash)
**題目:**給定一個字符串數組,將字母異位詞組合在一起。字母異位詞指字母相同,但排列不同的字符串。
示例:
輸入: ["eat", "tea", "tan", "ate", "nat", "bat"]
輸出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
說明:
所有輸入均爲小寫字母。
不考慮答案輸出的順序。
代碼:
public class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
//用於存放結果
List<List<String>> res = new ArrayList<>();
if(strs.length==0 || strs==null) return null;
//鍵用於存放排序後的字符串,值用於存放該字符串在res的位置
HashMap<String,Integer> map = new HashMap<>();
for(int i=0;i<strs.length;i++){
//將字符串轉換爲數組,使用Array工具類對字符串按照字典順序進行排序
char[] chars = strs[i].toCharArray();
Arrays.sort(chars);
//重新轉換爲字符串
String s = String.valueOf(chars);
//如果map中沒有包含s
if(!map.containsKey(s)){
ArrayList<String> list = new ArrayList<>();
list.add(strs[i]);
map.put(s,res.size());
res.add(list);
}else {
//map.get(s)代表獲取res中存放s的數組索引
//res.get(0)通過索引獲取res中存放的list
List<String> list = res.get(map.get(s));
//將strs[i]存放到list中
list.add(strs[i]);
}
}
return res;
}
}
50、Pow(x, n)(分治算法)
**題目:**實現 pow(x, n) ,即計算 x 的 n 次冪函數。
示例 1:
輸入: 2.00000, 10
輸出: 1024.00000
示例 2:
輸入: 2.10000, 3
輸出: 9.26100
示例 3:
輸入: 2.00000, -2
輸出: 0.25000
解釋: 2-2 = 1/22 = 1/4 = 0.25
說明:
-100.0 < x < 100.0
n 是 32 位有符號整數,其數值範圍是 [−231, 231 − 1] 。
思路:
代碼:
class Solution {
public double myPow(double x, int n) {
if(n>0){
return pow(x,n);
}else{
return 1/pow(x,n);
}
}
private double pow(double x, int n) {
if(n==0) return 1;
double y = pow(x,n/2);
//如果n爲偶數:2^2 = 2^1 * 2^1=(2^0 *2^0 *2) * (2^0 *2^0 *2)=4
if(n%2==0){
return y*y;
//如果n爲奇數:2^3 = 2^1 * 2^1 * 2=(2^0 * 2^0 *2) * (2^0 *2^0 *2)*2=8
}else {
return y*y*x;
}
}
}
53、最長子序和(動態規劃!)
**題目:**給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。
輸入: [-2,1,-3,4,-1,2,1,-5,4],
輸出: 6
解釋: 連續子數組 [4,-1,2,1] 的和最大,爲 6。
**思路:**動態規劃解法
此處的狀態轉移方程也可以切換爲 dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]);
因爲是需要最大和的連續子數組,因此可以保存每個數組元素所在位置能夠得到的最大連續元素和,這個最大值可以通過判斷前一個最大值是否爲負數來判斷(因爲負數無論加任何數都比當前數小),如果爲負,當前元素的最大和就是其本身。
//寫法1:
public class Solution {
public int maxSubArray(int[] nums) {
if(nums.length==0||nums==null) return 0;
//創建dp表
int[] dp = new int[nums.length];
dp[0] = nums[0];
int res = dp[0];
//遍歷數組
for(int i=1;i<nums.length;i++){
dp[i] = Math.max(dp[i-1]+nums[i],nums[i]);
res = Math.max(dp[i],res);
}
return res;
}
}
//寫法2:
public class Solution {
public int maxSubArray(int[] nums) {
if(nums.length==0||nums==null) return 0;
//創建dp表
int[] dp = new int[nums.length];
dp[0] = nums[0];
int res = dp[0];
//遍歷數組
for(int i=1;i<nums.length;i++){
dp[i] = dp[i-1]>0 ? dp[i-1]+nums[i]:nums[i];
res = Math.max(dp[i],res);
}
return res;
}
}
54、螺旋矩陣(二維數組)
**題目:**給定一個包含 m x n 個元素的矩陣(m 行, n 列),請按照順時針螺旋順序,返回矩陣中的所有元素。
示例 1:
輸入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
輸出: [1,2,3,6,9,8,7,4,5]
示例 2:
輸入:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]
輸出: [1,2,3,4,8,12,11,10,9,5,6,7]
思路:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-n8X9KEP3-1593674020532)(F:\秋招面試準備\assets\image-20200616132224350.png)]
//time:O(m*n)
//space:O(m*n)
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> list = new ArrayList<>();
if(matrix==null || matrix.length==0 || matrix[0].length==0 || matrix[0]==null){
return list;
}
int rowBegin = 0;
int rowEnd = matrix[0].length-1;
int colBegin = 0;
int colEnd = matrix.length-1;
while (true){
for(int i=rowBegin;i<=rowEnd;i++) {
//將最上面一行加入list
list.add(matrix[colBegin][i]);
}
//加入後將該行去掉
colBegin++;
if(colBegin>colEnd) break;
for(int i=colBegin;i<=colEnd;i++){
//將最右面一行加入list
list.add(matrix[i][rowEnd]);
}
//加入後將該列去掉
rowEnd--;
if(rowEnd<rowBegin) break;
for(int i=rowEnd;i>=rowBegin;i--){
//將最下面一行加入list
list.add(matrix[colEnd][i]);
}
//加入後將該行去掉
colEnd--;
if(colEnd<colBegin) break;
for(int i=colEnd;i>=colBegin;i--){
//將最左面一行加入list
list.add(matrix[i][rowBegin]);
}
//加入後將該列去掉
rowBegin++;
if(rowBegin>rowEnd) break;
}
return list;
}
}
55、跳躍遊戲(貪心算法)
題目:給定一個非負整數數組,你最初位於數組的第一個位置。數組中的每個元素代表你在該位置可以跳躍的最大長度。判斷你是否能夠到達最後一個位置。
示例 1:
輸入: [2,3,1,1,4]
輸出: true
解釋: 我們可以先跳 1 步,從位置 0 到達 位置 1, 然後再從位置 1 跳 3 步到達最後一個位置。
示例 2:
輸入: [3,2,1,0,4]
輸出: false
解釋: 無論怎樣,你總會到達索引爲 3 的位置。但該位置的最大跳躍長度是 0 , 所以你永遠不可能到達最後一個位置。
代碼:
public class Solution {
public boolean canJump(int[] nums) {
if(nums==null) return false;
//最大可以跳的步數
int max = 0;
for(int i=0;i<nums.length;i++){
if(i>max) return false;
max = Math.max(nums[i]+i,max);
}
return true;
}
}
56、合併區間(重要!!)
**題目:**給出一個區間的集合,請合併所有重疊的區間。
示例 1:
輸入: [[1,3],[2,6],[8,10],[15,18]]
輸出: [[1,6],[8,10],[15,18]]
解釋: 區間 [1,3] 和 [2,6] 重疊, 將它們合併爲 [1,6].
示例 2:
輸入: [[1,4],[4,5]]
輸出: [[1,5]]
解釋: 區間 [1,4] 和 [4,5] 可被視爲重疊區間。
思路:
需要對所有的區間按照左端點升序排序,然後遍歷。
如果當前遍歷到的區間的左端點 > 結果集中最後一個區間的右端點,說明它們沒有交集,把區間添加到結果集;
|----| |-----|
1 4 1 3
|-----| |-----|
3 6 5 7
如果當前遍歷到的區間的左端點 <= 結果集中最後一個區間的右端點,說明它們有交集,此時產生合併操作,即:對結果集中最後一個區間的右端點更新(取兩個區間的最大值)。
在具體的算法描述中:
前提:區間按照左端點排序;
貪心策略:在右端點的選擇中,如果產生交集,總是將右端點的數值更新成爲最大的,這樣就可以合併更多的區間,這種做法是符合題意的。
代碼:
class Solution {
public int[][] merge(int[][] intervals) {
if(intervals==null || intervals.length==0) return intervals;
//對數組的頭進行升序排序
Arrays.sort(intervals, new Comparator<int[]>() {
/**
* 當前對象>比較對象,則返回1;當這樣是升序排序的。
* 當前對象>比較對象,則返回-1;這樣是降序排序的
*/
@Override
public int compare(int[] o1, int[] o2) {
return o1[0]-o2[0];
}
});
//使用list存放合併區間後的結果
List<int[]> res = new ArrayList<>();
res.add(intervals[0]);
//遍歷二維數組
for(int i=0;i<intervals.length;i++){
int[] currentInterval = intervals[i];
int[] peek = res.get(res.size()-1);
//如果當前遍歷到的區間的左端點 > 結果集中最後一個區間的右端點,說明它們沒有交集,把區間添加到結果集;
if(currentInterval[0]>peek[1]){
res.add(currentInterval);
}else {
//如果當前遍歷到的區間的左端點 <= 結果集中最後一個區間的右端點,說明它們有交集,此時產生合併操作
//即:對結果集中最後一個區間的右端點更新(取兩個區間的最大值)。
peek[1] = Math.max(currentInterval[1],peek[1]);
}
}
//將鏈表轉化爲二維數組的方式
return res.toArray(new int[res.size()][]);
}
}
57、插入區間(重要!!)
給出一個*無重疊的 ,*按照區間起始端點排序的區間列表。
在列表中插入一個新的區間,你需要確保列表中的區間仍然有序且不重疊(如果有必要的話,可以合併區間)。
示例 1:
輸入: intervals = [[1,3],[6,9]], newInterval = [2,5]
輸出: [[1,5],[6,9]]
示例 2:
輸入: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
輸出: [[1,2],[3,10],[12,16]]
解釋: 這是因爲新的區間 [4,8] 與 [3,5],[6,7],[8,10] 重疊。
|--| |---| |---| |---| |---|
1 2 3 5 6 7 8 10 12 16
|----------|
4 8
代碼:
public class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
List<int[]> res = new ArrayList<>();
/**
* |--| |---| |---| |---| |---|
* 1 2 3 5 6 7 8 10 12 16
* |----------|
* 4 8
*/
int i = 0;
//對於左邊,如果當前區間的右端點<插入區間的左端點,直接插入結果集中
while (i < intervals.length && intervals[i][1] < newInterval[0]) {
res.add(intervals[i]);
i++;
}
int[] temp = new int[]{newInterval[0],newInterval[1]};
//對於中間,如果當前區間的左端點小於插入區間的右端點,就更新插入區間
while (i < intervals.length && intervals[i][0] <= newInterval[1]) {
temp[0] = Math.min(intervals[i][0], temp[0]);
temp[1] = Math.max(intervals[i][1], temp[1]);
i++;
}
res.add(temp);
//對於右邊(當前區間的左端點>插入區間的右端點)
while (i < intervals.length) {
res.add(intervals[i]);
i++;
}
//將鏈表轉換爲二維數組
return res.toArray(new int[res.size()][]);
}
}
58、最後一個單詞的長度
給定一個僅包含大小寫字母和空格 ' '
的字符串 s
,返回其最後一個單詞的長度。如果字符串從左向右滾動顯示,那麼最後一個單詞就是最後出現的單詞。如果不存在最後一個單詞,請返回 0 。
**說明:**一個單詞是指僅由字母組成、不包含任何空格字符的 最大子字符串。
示例:
輸入: "Hello World"
輸出: 5
代碼:
class Solution {
public int lengthOfLastWord(String s) {
/***
* abc def
* 0123456
*
* 7-3-1=3
*
* lastIndexOf()返回指定字符在此字符串中最後一次出現處的索引。
*/
return s.trim().length()-s.trim().lastIndexOf(" ")-1;
}
}
59、螺旋矩陣II(二維數組)
**題目:**給定一個正整數 n,生成一個包含 1 到 n2 所有元素,且元素按順時針順序螺旋排列的正方形矩陣。
示例:
輸入: 3
輸出:
[
[ 1, 2, 3 ],
[ 8, 9, 4 ],
[ 7, 6, 5 ]
]
代碼:
public class Solution {
public int[][] generateMatrix(int n) {
//和原來的螺旋矩陣解法相同,不同的是那題是遍歷二維數組,這題是給二維數組賦值
int[][] martix = new int[n][n];
int rowBegin = 0;
int rowEnd = n-1;
int colBegin = 0;
int colEnd = n-1;
int num = 1;
while (true){
for(int i=colBegin;i<=colEnd;i++){
martix[rowBegin][i] = num++;
}
rowBegin++;
if(rowBegin>rowEnd) break;
for(int i=rowBegin;i<=rowEnd;i++){
martix[i][colEnd] = num++;
}
colEnd--;
if(colEnd<colBegin) break;
for(int i=colEnd;i>=colBegin;i--){
martix[rowEnd][i] = num++;
}
rowEnd--;
if(rowEnd<rowBegin) break;
for(int i=rowEnd;i>=rowBegin;i--){
martix[i][colBegin] = num++;
}
colBegin++;
if(colBegin>colEnd) break;
}
return martix;
}
}