目录
目录
排序
搜索旋转排序数组(力扣33)
整数数组
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
思路:见下面三条定理
//定理一:只有在顺序区间内才可以通过区间两端的数值判断 target 是否在其中。
//定理二:判断顺序区间还是乱序区间,只需要对比 left 和 right 是否是顺序对即可, left <= right ,顺序区间,否则乱序区间。
//定理三:每次二分都会至少存在一个顺序区间。
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length -1;
while(left <= right) {
int mid = left + (right-left)/2;
if (nums[mid] == target) {
return mid;
}
//left到mid是上升区间
if (nums[left] <= nums[mid]) {
if (target >= nums[left] && target <= nums[mid]) {
right = mid -1;
} else {
left = mid + 1;
}
} else {
//mid到right是上升区间
if (target >= nums[mid] && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
}
在排序数组中查找元素的第一个和最后一个位置(力扣34)
给你一个按照非递减顺序排列的整数数组
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]
思路:二分两次,一次找最左,一次找最右
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
// first和last分别表示 首次 和 最后 出现的位置
int first = -1;
int last = -1;
while(left <= right) {
int mid = left + (right - left)/2;
if (nums[mid] == target) {
first = mid;
right = mid - 1; //重点,不停向左缩放
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
left = 0;
right = nums.length - 1;
while(left <= right) {
int mid = left + (right - left)/2;
if (nums[mid] == target) {
last = mid;
left = mid + 1; //重点,不停向右缩放
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return new int[]{first, last};
}
}
寻找旋转排序数组中的最小值(力扣153)
已知一个长度为
n
的数组,预先按照升序排列,经由1
到n
次 旋转 后,得到输入数组。例如,原数组nums = [0,1,2,4,5,6,7]
在变化后可能得到:
- 若旋转
4
次,则可以得到[4,5,6,7,0,1,2]
- 若旋转
7
次,则可以得到[0,1,2,4,5,6,7]
注意,数组
[a[0], a[1], a[2], ..., a[n-1]]
旋转一次 的结果为数组[a[n-1], a[0], a[1], a[2], ..., a[n-2]]
。给你一个元素值 互不相同 的数组
nums
,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。你必须设计一个时间复杂度为
O(log n)
的算法解决此问题。示例 1:
输入:nums = [3,4,5,1,2] 输出:1 解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。示例 2:
输入:nums = [4,5,6,7,0,1,2] 输出:0 解释:原数组为 [0,1,2,4,5,6,7] ,旋转 3 次得到输入数组。示例 3:
输入:nums = [11,13,15,17] 输出:11 解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
思路:也是看顺序数组,最小节点一定在非顺序的那一侧
class Solution {
public int findMin(int[] nums) {
int left = 0;
int right = nums.length - 1;
int result = nums[0];
while(left <= right) {
int mid = left + (right - left)/2;
//左边是顺序数组,left->mid
if (nums[left] <= nums[mid]) {
result = Math.min(result, nums[left]);
left = mid + 1;
} else {
//右边是顺序数组,mid->right
result = Math.min(result, nums[mid]);
right = mid -1;
}
}
return result;
}
}
字符串
无重复字符的最长子串(力扣3)
给定一个字符串
s
,请你找出其中不含有重复字符的 最长子串
的长度。示例 1:
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。示例 2:
输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。示例 3:
输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke"是一个子序列,不是子串。
思路:用一个map维护每个字母最后出现的下标
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s.length() <= 1) {
return s.length();
}
int left = 0;
int result = 0;
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i< s.length(); i++) {
char current = s.charAt(i);
if (!map.containsKey(current)) {
map.put(current, i);
} else {
//如'abba',遍历到第二个a时,如果不max,left会回退
left = Math.max(left, map.get(current)+1);
map.put(current, i);
}
result = Math.max(result, i-left+1);
}
return result;
}
}
最长连续序列(力扣128)
给定一个未排序的整数数组
nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为O(n)
的算法解决此问题。示例 1:
输入:nums = [100,4,200,1,3,2] 输出:4 解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1] 输出:9
思路:
class Solution {
public int longestConsecutive(int[] nums) {
if (nums.length == 0) {
return 0;
}
Set<Integer> set = new HashSet<>();
for(int i : nums) {
set.add(i);
}
int result = 1;
for (int i : nums) {
if (set.contains(i-1)) {
continue;
}
int temp = 1;
while(set.contains(i+1)) {
i++;
temp++;
}
result = Math.max(result, temp);
}
return result;
}
}
数组
缺失的第一个正数(力扣41)
给你一个未排序的整数数组
请你实现时间复杂度为nums
,请你找出其中没有出现的最小的正整数。O(n)
并且只使用常数级别额外空间的解决方案。示例 1:
输入:nums = [1,2,0] 输出:3 解释:范围 [1,2] 中的数字都在数组中。示例 2:
输入:nums = [3,4,-1,1] 输出:2 解释:1 在数组中,但 2 没有。示例 3:
输入:nums = [7,8,9,11,12] 输出:1 解释:最小的正数 1 没有出现。
思路:缺失的数一定在[1,N+1]的区间中,其中N为数组长度
- 怎么理解:如果1在数组里,由于需要连续,假设[1,N]都在数组里,那么最坏情况N+1是缺失的最小数字。如果1不在数组里,那么1是缺失的最小数字
- swap数组,把数字i放到i-1的位置。最后数组变成[1,2,3,4...N]
class Solution {
public int firstMissingPositive(int[] nums) {
if (nums.length == 0) {
return 1;
}
for (int i = 0; i< nums.length; i++) {
//nums[i]-1 应该与 i 交换,如果nums[0] = 3,则交换下标0与2
while (nums[i]-1 >=0 && nums[i]-1 < nums.length) {
if (nums[nums[i]-1] == nums[i]) {
break;
}
swap(nums, i, nums[i]-1);
}
}
for (int i = 0; i< nums.length; i++) {
if (nums[i] != i+1) {
return i+1;
}
}
return nums.length +1;
}
public void swap(int[] nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
合并区间(力扣56)
以数组
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) {
if (intervals.length == 0 || intervals[0].length == 0) {
return new int[0][0];
}
Arrays.sort(intervals, new Comparator<int[]>(){
public int compare(int[] a, int[] b) {
return a[0] - b[0];
}
});
List<int[]> list = new ArrayList<>();
list.add(intervals[0]);
for (int i = 1; i< intervals.length; i++) {
int[] current = list.get(list.size() - 1);
if (intervals[i][0] <= current[1]) {
current[1] = Math.max(intervals[i][1], current[1]);
} else {
list.add(intervals[i]);
}
}
int[][] result = new int[list.size()][2];
for (int i = 0; i< list.size(); i++) {
result[i][0] = list.get(i)[0];
result[i][1] = list.get(i)[1];
}
return result;
}
}
加油站(力扣134)
在一条环路上有
n
个加油站,其中第i
个加油站有汽油gas[i]
升。你有一辆油箱容量无限的的汽车,从第
i
个加油站开往第i+1
个加油站需要消耗汽油cost[i]
升。你从其中的一个加油站出发,开始时油箱为空。给定两个整数数组
gas
和cost
,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回-1
。如果存在解,则 保证 它是 唯一 的。示例 1:
输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2] 输出: 3 解释: 从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油 开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油 开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油 开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油 开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。 因此,3 可为起始索引。示例 2:
输入: gas = [2,3,4], cost = [3,4,3] 输出: -1 解释: 你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。 我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油 开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油 开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油 你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。 因此,无论怎样,你都不可能绕环路行驶一周。
思路:
-
首先判断总gas能不能大于等于总cost,如果总gas不够,一切都白搭对吧(总(gas- cost)不用单独去计算,和找最低点时一起计算即可,只遍历一次);
-
再就是找总(gas-cost)的最低点,不管正负(当然如果最低点都是正的话那肯定能跑完了);也就是说:亏空最严重的一个点必须放在最后一步走,等着前面剩余的救助
-
找到最低点后,如果有解,那么解就是最低点的下一个点,因为总(gas-cost)是大于等于0的,所以前面损失的gas我从最低点下一个点开始都会拿回来!别管后面的趋势是先加后减还是先减后加,最终结果我是能填平前面的坑的。
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int minPoint = Integer.MAX_VALUE;
int minIndex = -1;
int total = 0;
for (int i = 0; i<gas.length; i++) {
total = total + gas[i] - cost[i];
if ( total <= minPoint) {
minPoint = total;
minIndex = i;
}
}
if (total < 0) {
return -1;
}
return (minIndex + 1) % gas.length;
}
}
树
二叉树展开为链表(力扣114)
给你二叉树的根结点
root
,请你将它展开为一个单链表:
- 展开后的单链表应该同样使用
TreeNode
,其中right
子指针指向链表中下一个结点,而左子指针始终为null
。- 展开后的单链表应该与二叉树 先序遍历 顺序相同。
示例 1:
输入:root = [1,2,5,3,4,null,6] 输出:[1,null,2,null,3,null,4,null,5,null,6]示例 2:
输入:root = [] 输出:[]示例 3:
输入:root = [0] 输出:[0]
class Solution {
public void flatten(TreeNode root) {
while(root != null) {
if (root.left == null) {
root = root.right;
} else {
TreeNode pre = root.left;
while(pre.right != null) {
pre = pre.right;
}
pre.right = root.right;
root.right = root.left;
root.left = null;
root = root.right;
}
}
}
}
双指针
三数之和(力扣15)
给你一个整数数组
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 。
- 细节:排序后如[-1,-1,0,1,1],如果前一个数与当前数相同,需要忽略,不然会重复
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums.length < 3) {
return result;
}
Arrays.sort(nums);
for (int i = 0; i < nums.length-2; i++) {
//如果前一个数重复了,忽略跳过
if (i!=0 && nums[i-1] == nums[i]) {
continue;
}
int left = i+1;
int right = nums.length -1;
while(left < right) {
//如果前一个数重复了,忽略跳过
if (left != i+1 && nums[left] == nums[left-1]) {
left++;
continue;
}
//如果前一个数重复了,忽略跳过
if (right != nums.length-1 && nums[right] == nums[right+1]) {
right--;
continue;
}
int temp = nums[left] + nums[right] + nums[i];
if(temp > 0) {
right--;
} else if (temp < 0) {
left++;
} else {
result.add(new ArrayList<>(Arrays.asList(nums[i], nums[left], nums[right])));
left++;
right--;
}
}
}
return result;
}
}
回溯
括号生成(力扣22)
数字
n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。示例 1:
输入:n = 3 输出:["((()))","(()())","(())()","()(())","()()()"]示例 2:
输入:n = 1 输出:["()"]
思路:剩余左括号总数要小于等于右括号
class Solution {
List<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
if(n <= 0){
return res;
}
getParenthesis("",n,n);
return res;
}
//left,right分别表示剩余的左括号,右括号数量
private void getParenthesis(String str,int left, int right) {
//左右括号全部用完时,加入res
if(left == 0 && right == 0 ){
res.add(str);
return;
}
if(left == right){
//剩余左右括号数相等,下一个只能用左括号
getParenthesis(str+"(",left-1,right);
}else if(left < right){
//剩余左括号小于右括号,下一个可以用左括号也可以用右括号
if(left > 0){
getParenthesis(str+"(",left-1,right);
}
getParenthesis(str+")",left,right-1);
}
}
}
组合总和(力扣39)
给你一个 无重复元素 的整数数组
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 输出: []
- 先对数组排序,然后回溯
class Solution {
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if (candidates.length == 0) {
return result;
}
//对数组先排序,方便回溯时剪枝
Arrays.sort(candidates);
backtrack(0, new ArrayList<>(), target, candidates);
return result;
}
public void backtrack(int start, List<Integer> current, int target, int[] candidates) {
// 当前的数组,已经满足和为target,加入答案里
if (target == 0) {
result.add(new ArrayList<>(current));
return;
}
// 遍历所有选择
// 剪枝二:从 start 开始遍历,避免生成重复子集
for (int i = start; i< candidates.length; ++i) {
// 剪枝一:若子集和超过 target ,则直接结束循环
// 这是因为数组已排序,后边元素更大,子集和一定超过 target
if (candidates[i] > target) {
return;
}
//开始回溯
current.add(candidates[i]);
//之所以是i,不是i+1,是因为数组元素可以重复使用
backtrack(i, current, target - candidates[i], candidates);
current.remove(current.size() - 1);
}
}
}
分割回文串(力扣131)
给你一个字符串
s
,请你将s
分割成一些子串,使每个子串都是 回文串。返回s
所有可能的分割方案。示例 1:
输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]示例 2:
输入:s = "a" 输出:[["a"]]
- 先生成boolean的dp[][],dp[i][j]表示字符串的[i,j]是否是回文的
- 再对dp进行回溯
class Solution {
List<List<String>> result = new ArrayList<>();
public List<List<String>> partition(String s) {
if (s.length() == 0) {
return result;
}
if (s.length() == 1) {
List<String> temp = new ArrayList<>();
temp.add(s);
result.add(temp);
return result;
}
//先生成二维回文的动态规划
boolean[][] dp = generateDp(s);
//回溯
backTrack(0, new ArrayList<>(), dp, s);
return result;
}
//dp[x][y]表示字符串[x,y]是否是回文的
public boolean[][] generateDp(String s) {
int length = s.length();
boolean[][] dp = new boolean[length][length];
//i代表长度,j代表从第j位开始循环遍历
for (int i = 1; i <= length; i++) {
for (int j = 0; j< length; j++) {
int end = j + i -1;
if (end >= length) {
break;
}
if (i == 1) {
dp[j][j] = true;
} else if (i == 2) {
dp[j][end] = s.charAt(j) == s.charAt(end);
} else {
dp[j][end] = dp[j+1][end-1] && s.charAt(j) == s.charAt(end);
}
}
}
return dp;
}
public void backTrack(int start, List<String> current, boolean[][] dp, String s) {
//string中所有字母已经处理完成,加到答案中
if (start == s.length()) {
result.add(new ArrayList<>(current));
return;
}
for (int i = start; i < s.length(); ++i) {
//如果从第start位到第i位,不是回文的:代表行不通,直接contine
if (!dp[start][i]) {
continue;
} else {
//如果从第start位到第i位,是回文的:从i+1开始回溯
current.add(s.substring(start, i+1));
backTrack(i+1, current, dp, s);
current.remove(current.size()-1);
}
}
}
}
动态规划
接雨水(力扣42)
给定
n
个非负整数表示每个宽度为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
思路:二维dp
class Solution {
public int trap(int[] height) {
if (height.length == 0) {
return 0;
}
int[] left = new int[height.length];
int[] right = new int[height.length];
left[0] = height[0];
right[height.length-1] = height[height.length-1];
int j;
for (int i = 1; i< height.length; i++) {
j = height.length -i -1;
left[i] = Math.max(left[i-1], height[i]);
right[j] = Math.max(right[j+1], height[j]);
}
int result = 0;
for (int i = 0; i < height.length; i++) {
result += Math.min(left[i],right[i]) - height[i];
}
return result;
}
乘积最大子数组(力扣152)
给你一个整数数组
nums
,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
示例 1:
输入: nums = [2,3,-2,4] 输出: 6 解释: 子数组 [2,3] 有最大乘积 6。示例 2:
输入: nums = [-2,0,-1] 输出: 0 解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
思路:同和最大子数组,dp[i]表示以第 i个元素结尾的乘积最大子数组的乘积
class Solution {
public int maxProduct(int[] nums) {
int[] dpmax = new int[nums.length];
int[] dpmin = new int[nums.length];
dpmax[0] = nums[0];
dpmin[0] = nums[0];
int result = nums[0];
for (int i = 1; i < nums.length; ++i) {
dpmax[i] = Math.max(Math.max(dpmax[i-1] * nums[i], dpmin[i-1] * nums[i]), nums[i]);
dpmin[i] = Math.min(Math.min(dpmax[i-1] * nums[i], dpmin[i-1] * nums[i]), nums[i]);
result = Math.max(result, dpmax[i]);
}
return result;
}
}
最长递增子序列(力扣300)
给你一个整数数组
nums
,找到其中最长严格递增子序列的长度。子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,
[3,6,2,7]
是数组[0,3,1,6,2,2,7]
的子序列。示例 1:
输入:nums = [10,9,2,5,3,7,101,18] 输出:4 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。示例 2:
输入:nums = [0,1,0,3,2,3] 输出:4示例 3:
输入:nums = [7,7,7,7,7,7,7] 输出:1
思路:. - 力扣(LeetCode)的方法一
- 定义dp[i]为考虑前 i 个元素,以第 i 个数字结尾的最长上升子序列的长度
- 例子:输入:nums = [10,9,2,5,3,7,101,18],dp = [1,1,1,2,2,3,4,4]
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums.length <= 1) {
return nums.length;
}
//定义一个result,在后面for循环中会不断更新result
int result = 1;
int[] dp = new int[nums.length];
dp[0] = 1;
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j< i; j++) {
dp[i] = 1;
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] +1);
}
}
result = Math.max(result, dp[i]);
}
return result;
}
}
贪心
跳跃游戏 II(力扣45)
给定一个长度为
n
的 0 索引整数数组nums
。初始位置为nums[0]
。每个元素
nums[i]
表示从索引i
向前跳转的最大长度。换句话说,如果你在nums[i]
处,你可以跳转到任意nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达
nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达nums[n - 1]
。示例 1:
输入: nums = [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。示例 2:
输入: nums = [2,3,0,1,4] 输出: 2
思路:. - 力扣(LeetCode)贪心,或者DP
//DP
class Solution {
public int jump(int[] nums) {
if (nums.length <= 1) {
return 0;
}
int[] dp = new int[nums.length];
Arrays.fill(dp, nums.length-1);
dp[0] = 0;
for (int i = 1; i< nums.length; i++) {
for (int j = i-1; j>=0; j--) {
if (j+nums[j] >= i) {
dp[i] = Math.min(dp[i], dp[j]+1);
}
}
}
return dp[nums.length-1];
}
}
//贪心
class Solution {
public int jump(int[] nums) {
int length = nums.length;
int end = 0;
int maxPosition = 0;
int steps = 0;
for (int i = 0; i < length - 1; i++) {
maxPosition = Math.max(maxPosition, i + nums[i]);
if (i == end) {
end = maxPosition;
steps++;
}
}
return steps;
}
}
划分字母区间(力扣763)
给你一个字符串
s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是
s
。返回一个表示每个字符串片段的长度的列表。
示例 1:输入:s = "ababcbacadefegdehijhklij" 输出:[9,7,8] 解释: 划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。 每个字母最多出现在一个片段中。 像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。示例 2:
输入:s = "eccbbbbdec" 输出:[10]
思路:维护一个int[26]数组,记录每个字母最后出现的位置
class Solution {
public List<Integer> partitionLabels(String s) {
List<Integer> result = new ArrayList<>();
if (s.length() == 0) {
return result;
}
int[] nums = new int[26];
for (int i = 0; i < s.length(); i++) {
nums[s.charAt(i)-'a'] = i;
}
//开始遍历
int start = 0;
int end = 0;
for (int i = 0; i< s.length(); i++) {
end = Math.max(end, nums[s.charAt(i)-'a']);
if(i == end) {
result.add(end- start +1);
start = end +1;
}
}
return result;
}
}
链表
K个一组翻转链表(力扣25)
给你链表的头节点
head
,每k
个节点一组进行翻转,请你返回修改后的链表。
k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是k
的整数倍,那么请将最后剩余的节点保持原有顺序。你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
输入:head = [1,2,3,4,5], k = 2 输出:[2,1,4,3,5]示例 2:
输入:head = [1,2,3,4,5], k = 3 输出:[3,2,1,4,5]
思路:. - 力扣(LeetCode)递归
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode tail = head;
for (int i = 1; i< k; i++) { //选取k个节点,需要向后移动了k-1次
if (tail == null) {
return head;
}
tail = tail.next;
}
if (tail == null) { //这里需要对第k个节点特判,否则会空指针
return head;
}
ListNode next = tail.next; //next指向第k+1个节点
tail.next = null;
ListNode newHead = reverse(head);
head.next = reverseKGroup(next, k);
return newHead;
}
//反转链表
private ListNode reverse(ListNode head) {
if (head == null) {
return head;
}
ListNode pre = null;
while(head != null) {
ListNode temp = head.next;
head.next = pre;
pre = head;
head = temp;
}
return pre;
}
}
矩阵
螺旋矩阵(力扣54)
给你一个
m
行n
列的矩阵matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,6,9,8,7,4,5]示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 输出:[1,2,3,4,8,12,11,10,9,5,6,7]
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> list = new ArrayList<>();
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return list;
}
int left =0;
int right = matrix[0].length -1;
int up = 0;
int down = matrix.length - 1;
while(true) {
for (int i =left; i<= right; i++) {
list.add(matrix[up][i]);
}
if (up+1>down) break;
up++;
for (int i = up; i<= down; i++) {
list.add(matrix[i][right]);
}
if (left+1>right) break;
right--;
for (int i = right; i>=left; i--) {
list.add(matrix[down][i]);
}
if (up+1>down) break;
down--;
for (int i = down; i>=up; i--) {
list.add(matrix[i][left]);
}
if (left+1>right) break;
left++;
}
return list;
}
}