237.删除链表中节点(中等)
题目描述:有一个单链表,给定节点node,需要删除这个node节点 题解:删除节点node必须找到前置节点,根据题意无法获取head节点,即不能获得前置节点,换个思路删除节点,也可以通过覆盖实现。这个题目最大挑战是能理解题意。
ListNode p = node;
ListNode q = p.next;
while (q.next != null) {
p.val = q.val;
p = q;
q = q.next;
}
p.val = q.val;
p.next = null;
53.最大子数组和
题目描述:整数数组 nums ,请你找出一个具有最大和的连续子数组
思路:动态规划法。 状态转移: f(i)=max{f(i−1)+nums[i],nums[i]}
public int maxSubArray(int[] nums) {
int pre = 0, maxAns = nums[0];
for (int x : nums) {
pre = Math.max(pre + x, x);
maxAns = Math.max(maxAns, pre);
}
return maxAns;
}
152 乘积最大子数组
题目描述:整数数组 nums ,请你找出数组中乘积最大的非空连续子数组,返回乘积
题解:暴力解法O(N^3)。 优化解法:使用s[i] 表示从0->i,且固定第i个元素结尾的最大连续子数组,s[i]求解需要O[N^2]
// s[i] 表示第i元素结尾的最大乘积
int[] s = new int[n];
for (int j = n - 1; j >= 0; j--) {
int tmp = nums[j];
int max = tmp;
for (int i = j - 1; i >= 0; i--) {
tmp *= nums[i];
max = Math.max(max, tmp);
}
s[j] = max;
}
再优化解法: 动态规划,状态转移f[i], g[i] f[i] 表示0->i,以x[i]结尾的最大乘积 g[i] 表示0->i,以x[i]结尾的最小乘积
for (int i = 1; i < n; i++) {
// dp
f[i] = Math.max(nums[i] * f[i - 1], nums[i] * g[i - 1]);
f[i] = Math.max(nums[i], f[i]);
g[i] = Math.min(nums[i] * f[i - 1], nums[i] * g[i - 1]);
g[i] = Math.min(nums[i], g[i]);
res = Math.max(res, f[i]);
}
1.3 完全平方数 LC279
题目描述:给一个整数n,返回和为n的完全平方数最少数量
题解:动态规划,难点是状态转移
/**
* 动态规划解法
* f[i] 表示最少需要平方数,其和为i
* f[i] = 1 + min(f[i- j^2])
*/
public int numSquares(int n) {
int[] f = new int[n+1];
f[1] = 1;
for (int i = 2; i <= n; i++) {
int min = Integer.MAX_VALUE;
for (int j = 1; j <= Math.sqrt(i); j++) {
min = Math.min(min, f[i - j * j]);
}
f[i] = 1 + min;
// System.out.println("index: " + i + " f[i]: " + f[i]);
}
return f[n];
}
64.最小路径和
题目:非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小 每次只能向下或者向右移动一步
思路:动态规划。dp[i][j]表示从start 到[i,j]位置的最小路径
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j-1]) + grid[i][j]; dp初始值状态:两个边界。
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
for (int i = 1; i < m; i++) {
dp[i][0] = dp[i-1][0] + grid[i][0];
}
for (int j = 1; j < n; j++) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j-1]) + grid[i][j];
}
}
return dp[m-1][n-1];
}
1143.最长公共子序列
题目:两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度
思路:
dp[i][j] 表示序列text1[0..i] 和 text2[0..j]的最长公共子序列长度 状态转移:
if (t1[i] == t2[j])
dp[i][j] = dp[i-1][j-1] + 1
else
dp[i][j] = Max(dp[i-1][j], dp[i][j-1])
初始状态:
for (int i = 0; i < m; i++) {
dp[i][0] = 0;
}
for (int j = 0; j < n; j++) {
dp[0][j] = 0;
}
583.字符串删除操作
题目:给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数
思路:删除最小步数,即找到最长公共子序列,剩下字符都需要删除
res = m - lcs + n - lcs
5. 最长回文子串
题目:一个字符串 s,找到 s 中最长的回文子串
思路: 状态转移
p[i][j] = p[i + 1][j - 1] && (s.charAt(i) == s.charAt(j));
第1步 p[i][j],注意遍历顺序j(n->0),i(0->j)
第2步 寻找最大值
for (int j = 0; j < n; j++) {
for (int i = 0; i <= j; i++) {
int len = (j + 1 - i);
if (len == 1) {
p[i][j] = true;
} else if (len == 2) {
p[i][j] = (s.charAt(i) == s.charAt(j));
} else {
p[i][j] = p[i + 1][j - 1] && (s.charAt(i) == s.charAt(j));
}
}
}
787. K站中转内最便宜的航班
思路:
核心:递归状态转移
final int INF = 10000 * 101 + 1;
// f[t][i]表示从src节点到i节点,通过t次航班,最小费用
for (int i = 0; i < k + 2; ++i) {
Arrays.fill(f[i], INF);
}
for (int t = 1; t <= k + 1; ++t) {
for (int[] flight : flights) {
int j = flight[0], i = flight[1], cost = flight[2];
f[t][i] = Math.min(f[t][i], f[t - 1][j] + cost);
}
}
快速排序
分治算法(快排): 1 分解:“划分”两个子区间,a[l... q-1]区间都小于等于分界a[q]、a[q+1 ...r]区间都大于等于分界a[q] 2 合并:由于都是原址排序,不需要执行合并
/* 哨兵划分 */
int partition(vector<int> &nums, int left, int right) {
// 以 nums[left] 为基准数
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] >= nums[left])
j--; // 从右向左找首个小于基准数的元素
while (i < j && nums[i] <= nums[left])
i++; // 从左向右找首个大于基准数的元素
swap(nums[i], nums[j]); // 交换这两个元素
}
swap(nums[i], nums[left]); // 将基准数交换至两子数组的分界线
return i; // 返回基准数的索引
}
/* 快速排序 */
void quickSort(vector<int> &nums, int left, int right) {
// 子数组长度为 1 时终止递归
if (left >= right)
return;
// 哨兵划分
int pivot = partition(nums, left, right);
// 递归左子数组、右子数组
quickSort(nums, left, pivot - 1);
quickSort(nums, pivot + 1, right);
}
差分
题目描述:n 个航班,它们分别从 1 到 n 进行编号,bookings bookings[i] = [firsti, lasti, seatsi] 表示从 firsti 到 lasti(包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。 请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。
input: bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5 output: [10,55,45,25,25]
思路:
差分数组diff[i]=nums[i]-nums[i-1], i从1开始,diff[0]=nums[0]
对区间[l, r] (l、r都从0开始)所有元素加val,等价于 diff[l]+=val, diff[r] -=val,特别注意才需要执行第2步
for(int[] booking : bookings) {
// 对区间[l,r] +val
int l = booking[0] - 1;
diff[l] += booking[2];
int r = booking[1] - 1;
if (r < n - 1) {
diff[r+1] -= booking[2];
}
}
int[] res = new int[n];
res[0] = diff[0];
for (int i=1; i< n; i++) {
res[i] = diff[i] + res[i-1];
}
链表系列
- 反转链表 思路:画图,逐个节点反转,prev、cur、next
prev = null;
cur = head;
while (cur != null) {
next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
876 链表的中间结点
思路: 解法1: 两次遍历,第1次统计长度n,第2次 n/2 遍历停止 解法2: 快慢指针 注意遍历边界 while(fast != null && fast.next != null)
876 重排链表
思路: 利用线性表下标访问特点,重建链表
双指针遍历l、r, 注意,一定要处理最后节点
技巧类型
287. 寻找重复数
思路:快慢指针 先设置慢指针slow 和快指针 fast,慢指针每次走一步,快指针每次走两步,根据「Floyd 判圈算法」两个指针在有环的情况下一定会相遇,此时我们再将 slow放置起点 0,两个指针每次同时移动一步,相遇的点就是答案。
int slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
4 股票买卖系列
121 买卖股票最佳时机 k=1
思路:画图,每个点的profit = right_max_values[i] - prices[i] 一次倒序遍历,获取right_max_values 一次正序遍历,获取全局最大res
122 买卖股票最佳时机 k=+inf
思路: 解法1 贪心算法 画图,可以发现一个规律,最大profit即每天如果上涨就买卖,局部利润最大保证全局利润最大
解法2 动态规划算法
int[][] dp = new int[n][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[n - 1][0];
5 动态规划系列
300. 最长递增子序列
思路:动态规划,dp[i]表示以num[i]结尾的最长递增子序列长度 状态转移方程,比较简单。O(N)
dp[0] = 1;
for (int i = 1; i < n; i++) {
int maxSeq = 0;
for (int j = 0; j < i; j++) {
if (nums[j] >= nums[i]) {
continue;
}
maxSeq = (maxSeq < dp[j]) ? dp[j] : maxSeq;
}
dp[i] = maxSeq + 1;
}
354. 俄罗斯套娃信封问题
思路:该题目是最长递增子序列的变种,简化二维问题:对数组排序,按照w升序、h降序。
理解:为什么当w相同时,按照h降序排列?题目要求套娃必须满足w1<w2, h1<h2,所以w相同时,h[i]不能在一个递增子序列
Arrays.sort(envelopes, (v1, v2) -> {
if (v1[0] != v2[0]) {
return (v1[0] - v2[0]);
} else {
return (v2[1] - v1[1]);
}
});
BFS算法系列
BFS算法场景:在图中找一条从start到target的短路径。 例如: 1 走迷宫问题。有格子的墙不能走,从起点到终点的最短路径 2 两个单词,允许替换字符,单词A最少替换几次可以变成另一个单词B 3 二叉树最小高度 4 解开密码锁的最小次数
111.二叉树的最小高度
简单题,熟悉算法 注意:树节点遍历不会出现重复,不需要visited
752.打开转盘锁
中等题 思路:BFS遍历 注意 相邻节点,从一个锁状态转到next 锁状态,有8个方向 易错点:修改数字字符'9'->'0', 需要把String转成char[],直接修改chs[i]