0%

剑指 Offer 60. n个骰子的点数

dp(真的妙)

对于n个骰子来说,他们值的和最小值是所有骰子都是1,最大值是所有骰子都是6,所以他们和的范围是[n, 6n],所以总共有6n - n + ! = 5n + 1种情况。

对于n个骰子的值x来说,设n-1个骰子值x的概率是f(n - 1, x),则f(n, x) = f(n - 1, x - 1) * 1 / 6 + f(n - 1, x - 2) * 1 / 6 ... f(n - 1, x - 6) * 1 / 6,这个式子表示当第n个骰子取值为y时,概率就是前n个骰子取值为x - y乘上当前骰子取y的概率,这样子就形成了递推公式。

但是x - y会产生越界问题,由此转变思想,当n-1个骰子取值为x时,n个骰子的取值只能是x+1 ~ x+6。由此不用考虑越界。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
vector<double> dicesProbability(int n) {
vector<double> pre(6, 1 / 6.0); // 初始化一个骰子的情况
for(int i = 2; i <= n; ++i) // 从第二个骰子开始
{
vector<double> tmp(5 * i + 1, 0); // i个骰子可能的情况共有5n+1种
for(int j = 0; j < pre.size(); ++j) // 对i-1个骰子情况中可能的取值进行遍历,表示递推式中的x
for(int k = 0; k < 6; ++k) // 对i-1个骰子可能的取值加上1到6,就是i个骰子情况,由于前面取值加上1到6可能会有重复情况,而把这些重复情况概率加起来就是最终概率。
tmp[k + j] += pre[j] * 1 / 6.0;
pre = tmp;
}
return pre;
}
};

剑指 Offer 59 - II. 队列的最大值

维护一个辅助队列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class MaxQueue {
public:
MaxQueue() {

}

int max_value() {
if(nodes.empty())
return -1;
return helper.front();
}

void push_back(int value) {
while(!helper.empty() && value > helper.back())
helper.pop_back();
helper.push_back(value);
nodes.push_back(value);
}

int pop_front() {
if(nodes.empty())
return -1;
if(helper.front() == nodes.front())
helper.pop_front();
auto ret = nodes.front();
nodes.pop_front();
return ret;
}
private:
deque<int> nodes; // 其实这个直接用queue也行
deque<int> helper;
};

/**
* Your MaxQueue object will be instantiated and called as such:
* MaxQueue* obj = new MaxQueue();
* int param_1 = obj->max_value();
* obj->push_back(value);
* int param_3 = obj->pop_front();
*/

剑指 Offer 65. 不用加减乘除做加法

位运算
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int add(int a, int b) {
int c;
while(b)
{
c = (unsigned int)(a & b) << 1; // leetcode的编译环境问题导致这里负数左移会报错,g++中则测试正常c = (a & b) << 1
a ^= b;
b = c;
}
return a;
}
};

由于使用的是补码可以统一负数和正数运算,所以不用考虑正负数。

考察位运算,首先可以知道对于加法器,如果不考虑进位,那么值就是a ^ b,而进位为a & b。则将不考虑进位算出来的值加上进位最终就是考虑进位的值,由于当前计算结果的进位是用于下一位的,所以需要将计算所得的进位左移一位,再次加,获得新的进位,知道进位为0,停止计算。

剑指 Offer 54. 二叉搜索树的第k大节点

左右反着来中序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int kthLargest(TreeNode* root, int k) {
stack<TreeNode*> q;
while(!q.empty() || root)
{
while(root)
{
q.push(root);
root = root->right;
}
root = q.top();
q.pop();
if(--k == 0)
return root->val;
root = root->left;
}
return 0;
}
};

剑指 Offer 48. 最长不含重复字符的子字符串

滑动窗口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_set<char> set;
int begin = 0, end = 0;
int ret = 0;
while(end < s.size())
{
if(set.find(s[end]) == set.end())
set.insert(s[end]);
else
{
while(s[begin] != s[end])
set.erase(s[begin++]);
++begin;
}
ret = max(ret, end - begin + 1);
++end;
}
return ret;
}
};
动态规划
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> map;
int ret = 0;
int now = 0;
for(int i = 0; i < s.size(); ++i)
{
if(map.find(s[i]) != map.end() && map[s[i]] >= i - now)
now = i - map[s[i]];
else
now = now + 1;
map[s[i]] = i;
ret = max(now, ret);
}
return ret;
}
};

剑指 Offer 53 - II. 0~n-1中缺失的数字

遍历
1
2
3
4
5
6
7
8
9
10
class Solution {
public:
int missingNumber(vector<int>& nums) {
int n = nums.size();
for(int i = 0; i < n; ++i)
if(nums[i] != i)
return i;
return n;
}
};

排序数组中的搜索问题,首先想到 二分法 解决。

二分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int missingNumber(vector<int>& nums) {
int n = nums.size();
int lo = 0, hi = n; // 这里hi不能是n-1,当所有数字全在范围内时,hi不会改变,这样一来lo会像hi靠近,这样返回的hi还是n,符合结果
while(lo < hi)
{
int mid = lo + ((hi - lo) >> 1);
if(nums[mid] != mid)
hi = mid;
else
lo = mid + 1;
}
return hi;
}
};

238. Product of Array Except Self

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
vector<int> ret(nums.size());
ret.front() = 1;
for(int i = 1; i < nums.size(); ++i)
ret[i] = ret[i - 1] * nums[i - 1];
int right = 1;
for(int i = nums.size() - 2; i >= 0; --i)
{
ret[i] *= right * nums[i + 1];
right *= nums[i + 1];
}
return ret;
}
};

对于数

1
2
3
Numbers:     2    3    4     5
Lefts: 2 2*3 2*3*4
Rights: 3*4*5 4*5 5
由此可见对于一个数的乘积,只需要计算他左边所有数的乘积和右边所有数的乘积,两者相乘就可以获得除了这个数外的乘积,而对于每一位来说,左右乘积都可以通过O(n)的两次遍历来达到,O(n)space的算法就是把左右的乘积放到left和right两个数组中

如果要将O(n)space降低到O(1)space,就需要把左乘积先存放在返回的数组中,然后从右往前遍历,对于每一个位置都在每次遍历时计算出一个右乘积,这个乘积ret中保存的左乘积,就可以直接得到答案