LeetCode每日一题

1310. 子数组异或查询

题解思路

使用前缀和

403. 青蛙过河

题解思路

我的问题是找不到dp值代表了什么.找不到状态转移方程
dp[i][k]代表了我是从[j]跳了k步到这里的,[j]有哪几种状态呢,从前面的状态跳了k + 1, k, k - 1步到[j][j]才有可能跳(k + 1) - 1,k,(k - 1) + 1步到[i]

dp[i][k] = dp[j][k - 1] || dp[j][k] || dp[j][k + 1];

需要注意的地方
1.k > j+1时,是不可能从j跳到i
2.因为是覆盖dp,所以ji - 10遍历

for (int i = 1; i < n; ++i) {
    for (int j = i - 1; j >= 0; --j) {
        int k = stones[i] - stones[j];
        if (k > j + 1)  break;
        dp[i][k] = dp[j][k - 1] || dp[j][k] || dp[j][k + 1];
        if (i == n - 1 && dp[i][k]) return true;
    }
}

下面的是正向思路,可能会好理解一点

for (int i = 1; i < n; ++i) {
    for (int j = i - 1; j >= 0; --j) {
        int k = stones[i] - stones[j];
        if (k > j + 1)  break;
        if (dp[j][k]) {
            dp[i][k - 1] = true;
            dp[i][k] = true;
            dp[i][k + 1] = true;
        }
        if (i == n - 1 && dp[i][k]) return true;
    }
}

633. 平方数之和

题解思路

暴力超时
使用双指针

while (a <= b) {
    if (a * a + b * b ==  c) {
        return true;
    }
    else if (a * a + b * b <  c) {
        a++;
    }
    else {
        b--;
    }
}

938. 二叉搜索树的范围和

我的思路
if (root != nullptr) {
    rangeSumBST(root -> left, low, high);
    if (root -> val >= low && root -> val <= high) {
        sum += root -> val;
    }
    rangeSumBST(root -> right, low, high);
}
return sum;

我本来的思路是从>= low开始 >=high结束的,这样似乎也是要加条件语句,我在最前面加了,时间并没有少多少,所以这题有什么意思?

1011. 在 D 天内送达包裹的能力

题解思路

本来以为运力一点一点加上去这样暴力的做法是不可以的
第二个问题是不知道具体地处理某一运力下天数的做法
暴力是可以的,只不过需要用二分来优化

1.left为最小运力maxelem,right为最大运力sumvec
2.在leftright中间有一个最小运力,使得所需天数==D
3.使用cur表示当前的货物量,货物量 > mid,所需的天数++(这里使用了>的小技巧,如果使用<=作为判断条件,需要写的会多一点)
4.所需天数>D,运力不够,left = mid + 1,所需天数<= D,运力说不定还能再减一点.right = mid

int left = *max_element(weights.begin(), weights.end()), right = accumulate(weights.begin(), weights.end(), 0);
while (left < right) {
    int mid = (left + right) / 2;
    int need = 1, cur = 0;
    for (int weight : weights) {
        if (cur + weight > mid) {
            need++;
            cur = 0;
        }
        cur += weight;
    }
    if (need <= D) {
        right = mid;
    }
    else if (need > D) {
        left = mid + 1;
    }
}

377. 组合总和 Ⅳ

我的思路

使用回溯方法,但是超时了。想要用记忆化递归,但是不知道存的是什么。
后来想了想存的是target,但是还是超时了

int sum(vector & nums, int target) {
        if (target < 0) {
            return 0;
        }
        else if (target == 0) {
            return 1;
        }         
        else if (mem[target] != 0){
            return mem[target];
        }

        int cnt = 0;
        for (int i = 0; i < nums.size(); ++i) {
            if (target >= nums[i])
                cnt += combinationSum4(nums, target - nums[i]);          
        }
        mem[target] = cnt;
        return cnt;
    }
题解思路

使用动态规划

vector dp(target + 1, 0);
dp[0] = 1;
for (int i = 1; i <= target; ++i) {
    for (const int& num : nums) {
        if (i - num >= 0 && dp[i] < INT_MAX - dp[i - num])
            dp[i] += dp[i - num];
    }
}
return dp[target];

使用限定条件INT_MAX - dp[i - num]去除比如说990的cnt比999要大且990 的cnt > INT_MAX的情况

368. 最大整除子集

题解思路

序列dp
使用f[]存最大长度,使用g[]存下标.j∈[0,i)如果可以整除并且f[j] + 1 > len就修改保存,遍历完j在[i]后保存len长度和prev下标.
即[i]下存着前一个可以整除的下标
需要注意的地方,初始化prev应该是i,我初始化为0是错误的,当它没有可以整除的数或者被作为第一个被整除的数的时候,存的下标应为自己.

for (int i = 0; i < n; ++i) {
    int len = 1, prev = i;
    for (int j = 0; j < i; ++j) {
        if (nums[i] % nums[j] == 0) {
            if (f[j] + 1 > len) {
                len = f[j] + 1;
                prev = j;
            }
        }
    }
    f[i] = len;
    g[i] = prev;
}

363. 矩形区域不超过 K 的最大数值和

我的思路

使用二维最大前缀和暴力解

for (int i = 1; i <= row; ++i) {
    for (int j = 1; j <= col; ++j) {
        dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + matrix[i - 1][j - 1];
    }
}
for (int i = 0; i <= row; ++i) {
    for (int j = 0; j <= col; ++j) {
        for (int ii = i + 1; ii <= row; ++ii) {
            for (int jj = j + 1; jj <= col; ++jj) {
                int tmp = dp[ii][jj] - dp[ii][j] - dp[i][jj] + dp[i][j];
                if (tmp <= k)
                    maxmatrix = max(maxmatrix, tmp);
            }
        }
    }
}

需要注意的是,前缀和矩阵不能从i``j开始从i + 1``j + 1开始才是一个边长为1的矩形,不然的话k < 0 ,结果找到一个0max就为0了

题解思路

先固定左边一排,右列逐渐向右。
通过最大连续子数组来剪枝,效果还挺好。最大连续子数组的max初始值记得设置为nums[0],不然,无论max是INT_MIN的话放dp上面还是下面都不好受

91. 解码方法

我的思路

分析一下,本题就是一个有条件的斐波那契数列,改了好久
例如
121 分为1 21 和12 1 f(n) = f(n - 1) + f(n - 2)

for (int i = 1; i < s.size(); ++i) {
    if (s[i] == '0') {
        if (s[i - 1] != '1' && s[i - 1] != '2') {
            return 0;
        }
        else {
            dp[i + 1] = dp[i - 1];
        }
    }
    else {
        if (s[i - 1] == '1' || s[i - 1] == '2' && s[i] - '0' < 7) {
            dp[i + 1] = dp[i] + dp[i - 1];
        }
        else {
            dp[i + 1] = dp[i];
        }
    }
}

28. 实现 strStr()

题解思路

KMP算法、暴力算法、BM算法

27. 移除元素

我的思路

使用pre作为新数组的最后一个元素。

220. 存在重复元素 III

####### 题解思路
我的想法是维护一个k宽度的map,因为map有自动排序功能,进一个出一个比较一个。
看了题解,思路没问题,就是自动排序也可以选择set

for (int i = 0; i < len; ++i) {
    auto iter = s.lower_bound((long long)nums[i] - t);
    if (iter != s.end() && *iter <= (long long)nums[i] + t) {
        return true;
    }
    s.insert(nums[i]);
    if (i >= k) { //可用s.size() > k来替换
        s.erase(nums[i - k]);
    }
}
  • 在排序好的set中查找到第一个大于等于nums[i] - t的位置,如果他存在且值小于num[i] + 1,就说明有这样的值
  • 注意等于号,包括 i >= k . *ter <= (long long)nums[i] + t

87. 扰乱字符串

思路

https://leetcode-cn.com/problems/scramble-string/solution/di-gui-by-powcai/
做递归,使用哈希表来完成记忆化递归,避免超时

213. 打家劫舍 II

我的思路
  • 不偷第一间
  • 不偷最后一间
max(dp2[len - 1], dp[len - 2]);

我还以为可以一次遍历解决的.然后发现题解和我的思路一样

208. 实现 Trie (前缀树)

思路

使用 next[26] 作为搜索节点、bool isend保存单词的终点。

783. 二叉搜索树节点最小距离

思路

中序遍历,两两相减的最小值

int minnum = INT_MAX;
void dfs(TreeNode* root) {
    if (root == nullptr) {
        return;
    } 
    if (root -> left) {
        dfs(root -> left);
    }
    if (prev != nullptr)
        minnum = min(minnum,root -> val - prev -> val);
    prev = root;
    if (root -> right){
        dfs(root -> right);
    }
}

179. 最大数

难点

转数字为字符串后 30 > 3 ==> 303 实际330更大
3331 > 3 ==> 33313 实际应为33331
以数位比较复杂繁琐,使用模拟的方法会更简易
cmp ==> s1 + s2 > s2 + s1

知识点
  • sort 比较默认从小到大
  • sort cmp函数return简单易记的方法 ,
大于(>)就是降序,小于(<)就是升序,
bool (int& a , int& b) {
  return a>b;
}
  • string字符串比较按ASCII码进行

264. 丑数 II

思路1

  • 使用map(不重复排序),每一次去除第一个a,放入2a,3a,5a。

思路2

  • 三指针,
    1.num2 = p2×2,num3 = p3×3,num5 = p5 × 5
    1.dp[i] = num2 num3 num5的最小值
    2.dp[i] == numx, px++

153. 寻找旋转排序数组中的最小值

思路

二分

74. 搜索二维矩阵

思路

由上到下,由左到右分别进行二分搜索
https://mp.weixin.qq.com/s/M1KfTfNlu4OCK8i9PSAmug

[left,right]

right = size() - 1;
while ( <= )
mid = left + (right - left) / 2
if (target < mid) 
  right = mid - 1
if (target > mid)
  left = mid + 1
else
  return mid;

return -1
[left, right)
[1,2,2,2,2,2,3] #搜索到最左边的2

right = size()
while ( < )
mid = left + (right - left) / 2
if (target < mid) 
  right = mid
if (target > mid)
  left = mid + 1
else
  right = mid;

return left

190. 颠倒二进制位

思路

输入 n 最右边的一位,赋给ans上它原本位置的对称位,右移一位。

191. 位1的个数

tips
  • n & (n - 1)使得最低位的1翻转

92. 反转链表 II

tips
  • 确定首尾状态的cur节点情况,然后再讨论中间经过了哪些过程。

115. 不同的子序列

54. 螺旋矩阵

tips
  • 使用矩阵元素个数来计算终点
我的做法
  • 使用switch模拟方向这个状态的转换,使用当前坐标和缩小的边界比较作为停止条件。遇到了单列无法继续的问题。要加单列的额外处理。
  • 其实这种做法忽略了状态改变的连续性,直接在while循环里依次添加不同方向的模拟循环即可,以可计算的矩阵元素数组作为截止条件

1178. 猜字谜

  • 使用位来表示字符串
  • 字符串的所有子集
while (n > 0)
  n = (n - 1) & str

(对顺序和重复次数无要求时)

1.使用map以int类型表示word中每个字符串对应的数以及其出现的次数.
2.在map中找到puzzle[i]的子集.

303. 区域和检索 - 数组不可变

  • 了解并掌握前缀和

304. 二维区域和检索 - 矩阵不可变

  • 二维前缀和多使用行首零和列首零减少单行和单列的if判断
  • 二维数组的动态定义

1047. 删除字符串中的所有相邻重复项

  • 栈的性质
  • 字符串的方法

你可能感兴趣的:(LeetCode每日一题)