一、力扣
1、分发糖果

先考虑一边贪心,再考虑另一边。
java
class Solution {
public int candy(int[] ratings) {
int n=ratings.length;
int[] left=new int[n];
left[0]=1;
for(int i=1;i<n;i++){
if(ratings[i]>ratings[i-1]){
left[i]=left[i-1]+1;
}else{
left[i]=1;
}
}
int[] right=new int[n];
right[n-1]=1;
for(int i=n-2;i>=0;i--){
if(ratings[i]>ratings[i+1]){
right[i]=right[i+1]+1;
}else{
right[i]=1;
}
}
int res=0;
for(int i=0;i<n;i++){
res+=Math.max(left[i],right[i]);
}
return res;
}
}
2、加油站

java
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int curSum = 0; // 当前油箱剩余油量(从当前起点出发到当前站的累计盈余)
int totalSum = 0; // 总油箱剩余油量(整个环路的累计盈余)
int index = 0; // 可能的起点索引
for (int i = 0; i < gas.length; i++) {
// 计算从当前起点到i站的油量盈余(gas[i] - cost[i])
curSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
// 如果当前剩余油量为负,说明从当前起点无法到达下一站
if (curSum < 0) {
// 将起点更新为下一站(i+1取模实现环形)
index = (i + 1) % gas.length;
// 重置当前剩余油量,从新起点重新开始累积
curSum = 0;
}
}
// 总剩余油量小于0,说明无法绕环路一周
return totalSum < 0 ? -1 : index;
}
}
3、K 次取反后最大化的数组和

java
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
Arrays.sort(nums);
int sum = 0;
// 遍历排序后的数组,有负值且还有转换次数就转正
for(int i = 0; i < nums.length; i++) {
if(nums[i] < 0 && k > 0) {
nums[i] = -1 * nums[i];
k--;
}
sum += nums[i];
}
// 再排序有三种情况 1. 转换次数已经用完 此时直接返回即可
// 2.转换次数没用完 还剩偶数次,此时没有负数了,直接返回即可
// 3.转换次数没用完 还剩偶数次,此时没有负数了,返回sum-2*最小数
Arrays.sort(nums);
return sum - (k % 2 == 0? 0 : 2 * nums[0]);
}
}
4、数位dp-数字 1 的个数


因为这个是统计每个生成的数字中1的个数,所以有第二个维度,每次dfs都是与前面怎么填无关的。
算法解析:
- 数位DP思想:将数字拆解为各个位,逐位处理并统计符合条件的情况
- 记忆化剪枝:存储中间状态避免重复计算,时间复杂度优化至O(log n)
- 状态定义:memo[i][cnt1] 表示从第i位开始,已统计cnt1个1时后续的可能情况
- 两种状态转移:
- 受限状态(isLimit=true):必须小于等于原数字对应位,后续状态继承限制
- 自由状态(isLimit=false):可以取0-9,后续状态不受限 关键点:只有自由状态的结果可以被记忆化存储,受限状态的结果具有唯一性无法复用
java
class Solution {
public int countDigitOne(int n) {
// 将数字转换为字符数组便于逐位处理
char[] s = Integer.toString(n).toCharArray();
int m = s.length;
// 记忆化数组:memo[i][cnt1] 表示从第i位开始,已统计cnt1个1时后续的1的总数
int[][] memo = new int[m][m];
for (int[] row : memo) {
Arrays.fill(row, -1); // -1 表示未计算状态
}
// 启动DFS:从最高位开始,初始统计0个1,处于受限状态
return dfs(0, 0, true, s, memo);
}
private int dfs(int i, int cnt1, boolean isLimit, char[] s, int[][] memo) {
// 递归终止:处理完所有位数时返回累计的1的个数
if (i == s.length) {
return cnt1;
}
// 记忆化检查:非限制状态下且已计算过的状态直接返回
if (!isLimit && memo[i][cnt1] >= 0) {
return memo[i][cnt1];
}
int res = 0;
// 确定当前位的上界:受限于原数字时取当前位值,否则取9
int up = isLimit ? s[i] - '0' : 9;
// 枚举当前位可能的所有数字(0~up)
for (int d = 0; d <= up; d++) {
// 递归处理下一位:
// i+1:移动到下一位
// cnt1 + (d==1 ? 1:0):累计当前位的1的数量
// isLimit && d==up:只有当前位达到上限时,下一位才继续受限
res += dfs(i + 1, cnt1 + (d == 1 ? 1 : 0), isLimit && d == up, s, memo);
}
// 仅当不受限时记录结果,因为受限状态的结果无法被复用
if (!isLimit) {
memo[i][cnt1] = res;
}
return res;
}
}
1. 数位dp模版

java
class Solution {
public long numberOfPowerfulInt(long start, long finish, int limit, String s) {
String low = Long.toString(start);
String high = Long.toString(finish);
int n = high.length();
low = "0".repeat(n - low.length()) + low; // 补前导零,和 high 对齐
long[] memo = new long[n];
Arrays.fill(memo, -1);
return dfs(0, true, true, low.toCharArray(), high.toCharArray(), limit, s.toCharArray(), memo);
}
private long dfs(int i, boolean limitLow, boolean limitHigh, char[] low, char[] high, int limit, char[] s, long[] memo) {
if (i == high.length) {
return 1;
}
if (!limitLow && !limitHigh && memo[i] != -1) {
return memo[i]; // 之前计算过
}
// 第 i 个数位可以从 lo 枚举到 hi
// 如果对数位还有其它约束,应当只在下面的 for 循环做限制,不应修改 lo 或 hi
int lo = limitLow ? low[i] - '0' : 0;
int hi = limitHigh ? high[i] - '0' : 9;
long res = 0;
if (i < high.length - s.length) { // 枚举这个数位填什么
for (int d = lo; d <= Math.min(hi, limit); d++) {
res += dfs(i + 1, limitLow && d == lo, limitHigh && d == hi, low, high, limit, s, memo);
}
} else { // 这个数位只能填 s[i-diff]
int x = s[i - (high.length - s.length)] - '0';
if (lo <= x && x <= hi) { // 题目保证 x <= limit,无需判断
res = dfs(i + 1, limitLow && x == lo, limitHigh && x == hi, low, high, limit, s, memo);
}
}
if (!limitLow && !limitHigh) {
memo[i] = res; // 记忆化 (i,false,false)
}
return res;
}
}
2. 前导零


java
class Solution {
public int countSpecialNumbers(int n) {
char[] s = Integer.toString(n).toCharArray();
int[][] memo = new int[s.length][1 << 10];
for (int[] row : memo) {
Arrays.fill(row, -1); // -1 表示没有计算过
}
return dfs(0, 0, true, false, s, memo);
}
private int dfs(int i, int mask, boolean isLimit, boolean isNum, char[] s, int[][] memo) {
if (i == s.length) {
return isNum ? 1 : 0; // isNum 为 true 表示得到了一个合法数字
}
if (!isLimit && isNum && memo[i][mask] != -1) {
return memo[i][mask]; // 之前计算过
}
int res = 0;
if (!isNum) { // 可以跳过当前数位
res = dfs(i + 1, mask, false, false, s, memo);
}
// 如果前面填的数字都和 n 的一样,那么这一位至多填数字 s[i](否则就超过 n 啦)
int up = isLimit ? s[i] - '0' : 9;
// 枚举要填入的数字 d
// 如果前面没有填数字,则必须从 1 开始(因为不能有前导零)
for (int d = isNum ? 0 : 1; d <= up; d++) {
if ((mask >> d & 1) == 0) { // d 不在 mask 中,说明之前没有填过 d
res += dfs(i + 1, mask | (1 << d), isLimit && d == up, true, s, memo);
}
}
if (!isLimit && isNum) {
memo[i][mask] = res; // 记忆化
}
return res;
}
}
5、灌溉花园的最少水龙头数目

java
class Solution {
public int minTaps(int n, int[] ranges) {
int[] rightMost = new int[n + 1];
for (int i = 0; i <= n; i++) {
int r = ranges[i];
if (i > r) rightMost[i - r] = i + r; // 由于 i 在不断变大,对于 i-r 来说,i+r 必然是它目前的最大值
else rightMost[0] = Math.max(rightMost[0], i + r);
}
int ans = 0;
int curRight = 0; // 已建造的桥的右端点
int nextRight = 0; // 下一座桥的右端点的最大值
for (int i = 0; i < n; i++) { // 如果走到 n-1 时没有返回 -1,那么必然可以到达 n
nextRight = Math.max(nextRight, rightMost[i]);
if (i == curRight) { // 到达已建造的桥的右端点
if (i == nextRight) return -1; // 无论怎么造桥,都无法从 i 到 i+1
curRight = nextRight; // 造一座桥
ans++;
}
}
return ans;
}
}
6、回文链表

java
class Solution {
public boolean isPalindrome(ListNode head) {
int n=0;
for(ListNode temp=head;temp!=null;temp=temp.next) n++;
ListNode mid=find(head);
if(n%2==1) mid=mid.next;
ListNode sec=reverse(mid);
while(sec!=null){
if(sec.val!=head.val){
return false;
}
sec=sec.next;
head=head.next;
}
return true;
}
public ListNode find(ListNode head){
ListNode fast=head;
ListNode slow=head;
ListNode pre=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
pre=slow;
slow=slow.next;
}
pre.next=null;
return slow;
}
public ListNode reverse(ListNode head){
ListNode pre=null;
while(head!=null){
ListNode nexthead=head.next;
head.next=pre;
pre=head;
head=nexthead;
}
return pre;
}
}
7、寻找峰值



java
class Solution {
public int findPeakElement(int[] nums) {
int n = nums.length;
// 初始化左右指针。由于峰值至少需要比较一个邻居,右边界设为n-2(最后一个元素无需右邻居)
int left = 0, right = n - 2;
while (left <= right) {
int mid = (left + right) / 2; // 计算中间位置
// 比较中间元素与其右侧元素
if (nums[mid] < nums[mid + 1]) {
// 右侧元素更大,说明峰值可能在[mid+1, right]区间
left = mid + 1;
} else {
// 中间元素不小于右侧,峰值可能在[left, mid-1]区间或mid本身
right = mid - 1;
}
}
// 循环结束时,left指向潜在峰值。由于每次移动都保证向更高方向搜索,最终left即为峰值位置
return left;
}
}
8、路径总和 II

不带回溯dfs:
java
class Solution {
private List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<Integer> path = new ArrayList<>();
dfs(root, targetSum, path);
return ans;
}
// targetSum和path是上面的节点传递下来的信息,ans是全局的
private void dfs(TreeNode root, int targetSum, List<Integer> path) {
if (root == null) {
return;
}
targetSum -= root.val;
path.add(root.val);
if (root.left == root.right) { // 当前节点是叶子节点
if (targetSum == 0)
ans.add(path);
return;
}
// 这题好坑啊,这里递归的话要写成new ArrayList<>(path),
// 不然path是pathSum中传入的全局变量了
dfs(root.left, targetSum, new ArrayList<>(path));
dfs(root.right, targetSum, new ArrayList<>(path));
}
}
带回溯dfs:
java
class Solution {
private List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<Integer> path = new ArrayList<>();
dfs(root, targetSum, path);
return ans;
}
// targetSum和path是上面的节点传递下来的信息,ans是全局的
private void dfs(TreeNode root, int targetSum, List<Integer> path) {
if (root == null) {
return;
}
targetSum -= root.val;
path.add(root.val);
if (root.left == root.right) { // 当前节点是叶子节点
if (targetSum == 0)
ans.add(new ArrayList<>(path)); // 这里要new一个新的保存path的对象!!!
// return; 回溯的话这里就不能return了,return会导致remove不了
}
dfs(root.left, targetSum, path);
dfs(root.right, targetSum, path);
// 恢复现场
path.remove(path.size() - 1);
}
}
9、岛屿的最大面积
java
class Solution {
int[][] direct={{1,0},{-1,0},{0,1},{0,-1}};
int m,n;
int[][] grid;
public int maxAreaOfIsland(int[][] grid) {
int res=0;
this.grid=grid;
m=grid.length;
n=grid[0].length;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]==1){
res=Math.max(res,dfs(i,j)) ;
}
}
}
return res;
}
public int dfs(int x,int y){
grid[x][y]=0;
int res=1;
for(var dir:direct){
int nx=x+dir[0];
int ny=y+dir[1];
if(nx>=0&&nx<m&&ny>=0&&ny<n&&grid[nx][ny]==1){
res+=dfs(nx,ny);
}
}
return res;
}
}