力扣刷题10-阶段总结

60 阅读8分钟

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);
}

差分

www.cnblogs.com/labuladong/…

题目描述: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,特别注意r<n1r<n-1才需要执行第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];
}

链表系列

  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]