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
m≤n
假设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
92∗10=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
p≤i的所有质因子
当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
p≤i的所有质因子,
所以
p
≤
i
p \leq i
p≤i, 就一定会有
p
p
p这个质因子一定被找出来了, 一定已经在primes
里了
然后在从小到大枚举质因子的时候, (for (int j = 0; i * primes[j] < n; j ++ )
这句话, 从小到大枚举质因子), 由于
p
≤
i
p \leq i
p≤i的所有质因子, 所以不可能存在比
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 i∗p一定会被筛掉, N = N p ∗ p N = \frac{N}{p} * p N=pN∗p 一定会被筛掉(这里把 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
pj∗i的最小质因子
所以就证明了 只会被最小的质因子
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();
}
};