Week 10. 第189-204题

189. 旋转数组

分析

1 2 3 4 5 6 7
向右边移动3位
5 6 7 1 2 3 4

移动k次, 相当于后面k个数移动到前面
当然先要对 k % n先
原地算法
当然想用原地算法的话, 每次移动1个, 移动k次, 这样时间复杂度O(n^2)

因为移动1次要搬运n - 1个数, k次的话要搬k(n - 1)次

将数组翻转的话, 可以用原地算法来做
第1次交换 第1, 第n - 1
2次交换, 第2, 第n - 2个数
当两个指针相遇的时候, 就结束了

所以可以原地算法翻转一段

那么能不能用翻转操作 实现题目的要求呢

先把整个数组翻转一遍
在这里插入图片描述
翻转完后, 左边是逆序的, 右边是逆序的
然后分别对左边做翻转, 右边做翻转

翻转2倍, O(n), 空间O(1)

code

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n = nums.size();
        k %= n;
        reverse(nums.begin(), nums.end()); // 先翻转整个字符串
        reverse(nums.begin(), nums.begin() + k); // 翻转前面部分
        reverse(nums.begin() + k, nums.end()); // 翻转后面那部分
    }
};

190. 颠倒二进制位

分析

有一种方式,
int -> string -> reverse - >int

但是有一种更简单的方式, 可以省去中间部分(string->reverse)
直接将int -> int

在这里插入图片描述

首先考虑如何取int的第k位
n >> k & 1

先取个位,放到新的最高位, 然后取十位, 放到新的第2位
假如答案是res, 相当于是在答案末尾+1位
res << 1(先将最后1位空出来) + (n >> k & 1) (加入当前位数)

code

class Solution {
public:
    uint32_t reverseBits(uint32_t n) {
        uint32_t res = 0;
        for (int i = 0; i < 32; i ++ )
            res = res * 2 + (n >> i & 1);
        return res;
    }   
};

191. 位1的个数

分析

每一位数判断, 每次对n可以/2, 所以logn

code (O(logn))

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int res = 0;
        for (int i = 0; i < 32; i ++ )
            res += (n >> i & 1);
        return res;
    }
};

分析(lowbit)

lobit会返回2进制最后1位1
x = 13 = ( 1101 ) 2 x = 13 = (1101)_2 x=13=(1101)2 , 那么lowbit(x) 会返回 ( 1 ) 2 (1)_2 (1)2
x = 12 = ( 1100 ) 2 x = 12 = (1100)_2 x=12=(1100)2 , 拿了lowbit(x) 会返回 ( 100 ) 2 (100)_2 (100)2, 就是返回 ( 8 ) 10 (8)_{10} (8)10
在这里插入图片描述

计算次数稍微少1些, 因为计算次数等于包含1的个数,
只有1个1的话, 只用计算1次, 不需要枚举32次

code(lowbit)

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int res = 0;
        while (n) res ++, n -= n & (-n);
        return res;
    }

};

198. 打家劫舍

分析

不能选相邻的
简单的dp
在这里插入图片描述

code

下标从1开始,

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n + 1), g(n + 1);
        for (int i = 1; i <= n; i ++ ){
            f[i] = g[i - 1] + nums[i - 1];// nums0开始所以错开
            g[i] = max(f[i - 1], g[i - 1]);
        }
        return max(f[n], f[n - 1]);
    }
};

199. 二叉树的右视图

分析

求从右边往左看的值.
注意: 不是求右链
因为可能是以下情况
在这里插入图片描述
所以就是用宽搜, 搜下每一层, 将每一层最后一个数记下来就可以了

code

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        if (!root) return {};
        queue<TreeNode*> q;
        q.push(root);
        vector<int> res;
        while (q.size()){
            int len = q.size();
            for (int i = 0; i < len; i ++ ){
                auto t = q.front(); q.pop();
                if (t->left) q.push(t->left);
                if (t->right) q.push(t->right);
                if (i == len - 1) res.push_back(t->val);
            }
        }
        return res;
    }
};

200. 岛屿数量

分析

code

class Solution {
public:
    int n, m;
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    int numIslands(vector<vector<char>>& grid) {
        n = grid.size(), m = grid[0].size();
        int res = 0;
        for (int i = 0; i < n ; i ++ )
            for (int j = 0; j < m; j ++ )
                if (grid[i][j] == '1') {
                    res ++;
                    dfs(grid, i, j);
                }

        return res;
    }
    void dfs(vector<vector<char>>& grid, int x, int y){
        grid[x][y] = '0';
        for (int i = 0; i < 4; i ++ ){
            int a = x + dx[i], b = y + dy[i];
            if (a >= 0 && a < n && b >= 0 && b < m && grid[a][b] == '1') dfs(grid, a, b);
        }

    }
};

201. 数字范围按位与

分析

脑筋急转弯, 看下
大概看下数据范围, m ≤ n m \leq n mn
假设m和n的前k个都是一样的

m = xxxxxx0…
n = xxxxxx1…

可以发现 xxxxxx0… 中最大数就是xxxxxx011111, 那么xxxxxx011111 >= m, (因为是m所有选择中最大的啊)

同理xxxxxx100000 <= n, 因为xxxxxx100000是最小的一个,
所以可以发现
xxxxxx011111
xxxxxx100000
必然是在[m, n]之间, 由于&运算有交换律, 所以先对这两个数&, 后面全是0
在这里插入图片描述
所以这题答案就有了, 找到m和n第1位答案不一样的地方, 从这1位不一样的地方到后面, 全部都是0, 前面都不变

code

class Solution {
public:
    int rangeBitwiseAnd(int m, int n) {
        int res = 0;
        for (int i = 30; i >= 0; i -- ){ // 从最高位开始枚举
            if ((m >> i & 1) != (n >> i & 1)) break;
            if (m >> i & 1) res += 1 << i;
        }
        return res;
    }
};

202. 快乐数

分析

最坏情况下, 假设有10个9, 999999999 也就是每次变完之后 = 9 2 ∗ 10 = 810 9^2 * 10 = 810 9210=810
所以每次操作完必然是0~810之间的数
那就是说数的个数不到1000个
所当操作812次之后, 必然会陷入循环
问题就是问 最后结束的地方是否是1

怎么去遍历圈呢
其实就是判断圈里是不是就是只有1个数1, 如果有1的话, 必然会自己循环
不可能有多个圈, 因为每个数变成的数是唯一的, 如果有多个圈的话, 交点就会有2个数, 矛盾了

同环形链表那里一样

同学课堂疑问: 不太懂, 为什么会有重复

yxc: 0 ~ 810一共811个数, 但是走了812步的话, 必然是出现812个不同的数, 总共只有811个不同的数, 因此一定重复了

联动题

leetcode141, 142

回到本题

所以可以用双指针来做, 一个慢指针, 一个快指针, 慢指针每次走1步, 快指针每次走2步, 当两个指针相遇的时候, 必然是环中的某个点, 判断下这个点是不是1

code

class Solution {
public:
    int get(int x){
        int res = 0;
        while (x){
            res += (x % 10) * (x % 10);
            x /= 10;
        }
        return res;
    }
    bool isHappy(int n) {
        int slow = n, fast = get(n); // 注意刚开始fast先走一步
        // 否则无法进入while循环
        while (slow != fast){
            fast = get(get(fast)), slow = get(slow); // fast走两部, slow走一步
        }
        return fast == 1;
    }


};

203. 移除链表元素

分析

每次从虚拟头节点开始遍历, 看下头节点的下一个点开始的一段 = 目标值的都略去
如果不等于的, 不用管,
在这里插入图片描述

在这里插入图片描述

code

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        auto dummy = new ListNode(-1);
        dummy->next = head;
        for (auto p = dummy; p; p = p->next){
            auto q = p->next; // 每次从当前点的下一个点开始搜
            while (q && q->val == val) q = q->next; // 如果==val, 就跳过
            p->next = q; // 然后让p指向q
        }
        return dummy->next;
    }
};

204. 计数质数

分析

首先一个判重数组, 来判断1 ~ n中这些数是不是被筛选过
所谓被筛过就是: 如果不是质数的话就打上标记true

正确性:

之所以是O(n)的, 是因为保证了每个和数一定会被最小值质因子筛, 并且值会被最小值质因子筛

线性筛法: 每个合数 N, 一定会被且只会被最小质因子筛, 而且只会被筛1次
这样的话, 只要证明这一点, 就能保证每个和数都会被筛掉, 一定会被筛的话, 那就证明了线性筛是正确的, 再一个就是每个和数有唯一的最小质因子, 且只会被最小质因子筛1次, 所以每个数只会被筛1次, 所以可以保证时间是O(n)的

每个数只会被筛1次, for (int j = 0; i * primes[j] < n; j ++ ) 加到一块只会循环n

首先, 一个质数一定不会被筛, i * primes[j]乘积一定是一个和数, 因为起码两个质因子
再一个

1.每个合数一定会被筛

假设: 合数用N表示, p是N的最小质因子

i = n p i = \frac{n}{p} i=pn, 由于p是N的最小质因子
那么 i i i的质因子, 一定是N的质因子, 因为i是N的约数, 所以i里面每个质因子, 一定是N的质因子
所以 p ≤ i p \leq i pi的所有质因子

for (int i = 2; i < n; i ++ ) 循环到 n / p n / p n/p的时候, 即循环到 i i i的时候, 因为 p p p是N的最小质因子, 又因为 i i i是N的约数, 所以 p ≤ i p \leq i pi的所有质因子,
所以 p ≤ i p \leq i pi, 就一定会有 p p p这个质因子一定被找出来了, 一定已经在primes里了
然后在从小到大枚举质因子的时候, (for (int j = 0; i * primes[j] < n; j ++ ) 这句话, 从小到大枚举质因子), 由于 p ≤ i p \leq i pi的所有质因子, 所以不可能存在比 p p p小的质因子primes[j], 使得(i % primes[j] == 0), 所以一定不会在p之前break

为什么可能存在比p小的质数因子呢, 因为i的所有质因数分解里的质数都要 >= p了, 最少也是p, 因此就不可能存在比p还小的数能够整除i

因此for (int j = 0; i * primes[j] < n; j ++ ) 循环中, primes[j]一定会枚举到 p p p

一定会枚举到 p p p的话, 那么 i ∗ p i * p ip一定会被筛掉, N = N p ∗ p N = \frac{N}{p} * p N=pNp 一定会被筛掉(这里把 i = N p i = \frac{N}{p} i=pN代入了)

2.合数N会被最小质因子筛几次(N是不是只会被最小质因子筛)

由于 if (i % primes[j] == 0) 一定会break, 然后p[j]是从小到大枚举的

所在执行st[i * primes[j]] = true;这句话的时候, 一定会有 p j p_j pj 一定 ≤ \leq i的所有质因子

因为如果说 p j > i p_j > i pj>i的某个质因子的话, 当枚举到 i i i的质因子的时候, 就会执行if (i % primes[j] == 0) 会break, 就会跳出j的循环, 枚举不到 p j p_j pj

因此 p j p_j pj一定是 p j ∗ i p_j * i pji的最小质因子
所以就证明了 只会被最小的质因子 p j p_j pj

3.只会被筛1次

N只会在 i = N p i = \frac{N}{p} i=pN, 且 j j j 枚举到 p j p_j pj的时候筛1次, 所以N只会被筛1次

code O(n)

class Solution {
public:
    int countPrimes(int n) {
        vector<bool> st(n + 1);
        vector<int> primes;
        for (int i = 2; i < n; i ++ ){
            if (!st[i]) primes.push_back(i); // 如果没被标记过, 那一定是质数
            // 因为上面说了啊, 标记过的都不是质数了, 那么剩下的没标记的都一定是质数了
            for (int j = 0; i * primes[j] < n; j ++ ){
                st[i * primes[j]] = true;
                if (i % primes[j] == 0) break;
            }
        }
        return primes.size();
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值