文章目录
-
-
- 数组的度(题目编号697:[link](https://leetcode-cn.com/problems/degree-of-an-array/))
- 二叉搜索树(题目编号700:[link](https://leetcode-cn.com/problems/search-in-a-binary-search-tree/))
- 数据流中的第K大元素(题目编号703:[link](https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/))
- 二分查找(题目编号704:[link](https://leetcode-cn.com/problems/binary-search/))
- 转换成小写字母(题目编号709:[link](https://leetcode-cn.com/problems/to-lower-case/))
- 1比特与2比特字符(题目编号717:[link](https://leetcode-cn.com/problems/1-bit-and-2-bit-characters/))
- 图像渲染(题目编号733:[link](https://leetcode-cn.com/problems/flood-fill/))
- 设计哈希集合(题目编号705:[link](https://leetcode-cn.com/problems/design-hashset/))
- 建立哈希映射(题目编号706:[link](https://leetcode-cn.com/problems/design-hashmap/))
- 翻转字符串里的单词(题目编号151:[link](https://leetcode-cn.com/problems/reverse-words-in-a-string/))
- 面试题 16.07. 最大数值([link](https://leetcode-cn.com/problems/maximum-lcci/))
- 在线选举(题目编号911:[link](https://leetcode-cn.com/problems/online-election/))
- 前 K 个高频元素(题目编号347:[link](https://leetcode-cn.com/problems/top-k-frequent-elements/))
- 形成两个异或相等数组的三元组数目(题目编号1442:[link](https://leetcode-cn.com/problems/count-triplets-that-can-form-two-arrays-of-equal-xor/))
- 最大数(题目编号179:[link]())
- 不同路径(题目编号62:[link](https://leetcode-cn.com/problems/unique-paths/))
- 不同路径II(题目编号63:[link](https://leetcode-cn.com/problems/unique-paths-ii/))
- 组合(题目编号77:[link](https://leetcode-cn.com/problems/combinations/))
- 单词搜索(题目编号79:[link](https://leetcode-cn.com/problems/word-search/))
- 找到K个最接近的元素(题目编号658:[link](https://leetcode-cn.com/problems/find-k-closest-elements/))
- 目标和(题目编号494:[link](https://leetcode-cn.com/problems/target-sum/),2021.7.7)
- 随机数索引(题目编号398:[link](https://leetcode-cn.com/problems/random-pick-index/),2021.7.7)
- 两数相加II(题目编号445:[link](https://leetcode-cn.com/problems/add-two-numbers-ii/))
- 移动所有球到每个盒子所需的最小操作数(题目编号1769:[link](https://leetcode-cn.com/problems/minimum-number-of-operations-to-move-all-balls-to-each-box/))
- 字母板上的路径(题目编号1138:[link](https://leetcode-cn.com/problems/alphabet-board-path/))
- 索引处的解码字符串(题目编号880:[link](https://leetcode-cn.com/problems/decoded-string-at-index/))
- 安排电影院座位(题目编号1386:[link](https://leetcode-cn.com/problems/cinema-seat-allocation/))
- 统计二叉树中好节点的数目(题目编号1448:[link](https://leetcode-cn.com/problems/count-good-nodes-in-binary-tree/))
- 飞机座分配概率(题目编号1227:[link](https://leetcode-cn.com/problems/airplane-seat-assignment-probability/))
- 连续的子数组和(题目编号523:[link](https://leetcode-cn.com/problems/continuous-subarray-sum/))
- 整数拆分(题目编号343:[link](https://leetcode-cn.com/problems/integer-break/))
- 最少侧跳次数(题目编号1824:[link](https://leetcode-cn.com/problems/minimum-sideway-jumps/),2021.7.28)
- 重新安排行程(题目编号332:[link](https://leetcode-cn.com/problems/reconstruct-itinerary/),2021.7.27)
- 将二叉搜索树变平衡(题目编号1382:[link](https://leetcode-cn.com/problems/balance-a-binary-search-tree/),2021.7.28)
- 课程表IV(题目编号1462:[link](https://leetcode-cn.com/problems/course-schedule-iv/),2021.7.28)
- 具有所有最深节点的最小子树(题目编号865:[link](https://leetcode-cn.com/problems/smallest-subtree-with-all-the-deepest-nodes/),2021.7.28)
- 两个字符串的删除操作(题目编号583:[link](https://leetcode-cn.com/problems/delete-operation-for-two-strings/),2021.7.28)
- 剑指Offer II020 回文字符串的个数(链接:[link](https://leetcode-cn.com/problems/a7VOhD/),2021.8.22)
- 剑指Offer II没有重复元素集合的全排列(题目编号083,链接:[link](https://leetcode-cn.com/problems/VvJkup/))
- 多次搜索(题目编号:面试题17.17,[link](https://leetcode-cn.com/problems/multi-search-lcci/),2021.8.22)
- 堆盘子(题目编号:面试题03.03,[link](https://leetcode-cn.com/problems/stack-of-plates-lcci/))
- 实现一个魔法字典(题目编号676:[link](https://leetcode-cn.com/problems/implement-magic-dictionary/),2021.8.22)
- 从英文中重建数字(题目编号:423:[link](https://leetcode-cn.com/problems/reconstruct-original-digits-from-english/),2021.8.27)
- 零钱兑换II(题目编号518:[link](https://leetcode-cn.com/problems/coin-change-2/),2021.8.27)
- 剑指Offer II 085. 生成匹配的括号(链接:[link](https://leetcode-cn.com/problems/IDBivT/),2021.8.27)
- 吃苹果的最大数目(题目编号1705:[link](https://leetcode-cn.com/problems/maximum-number-of-eaten-apples/),2021.8.27)
- 剑指Offer II 046. 二叉树的右侧视图(链接:[link](https://leetcode-cn.com/problems/WNC0Lk/),2021.8.27)
- 网络延迟时间(题目编号743:[link](https://leetcode-cn.com/problems/network-delay-time/),2021.8.27)
- 插入、删除和随机访问都是 O(1) 的容器(题目编号:剑指offerII 面试题30,[link](https://leetcode-cn.com/problems/FortPu/))
- 获取好友已观看的视频(题目编号1311:[link](https://leetcode-cn.com/problems/get-watched-videos-by-your-friends/))
- 子字符串突变后可能得到的最大整数(题目编号1946:[link](https://leetcode-cn.com/problems/largest-number-after-mutating-substring/))
- 解码异或后的排列(题目编号1734:[link](https://leetcode-cn.com/problems/decode-xored-permutation/),2021.9.26)
- 二叉搜索树与双向链表(题目编号:剑指offer36,[link](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/submissions/),2021.9.26)
- 最长连续序列(题目编号:剑指Offer II 119,[link](https://leetcode-cn.com/problems/WhsWhI/),2021.9.26)
- 扣分后的最大得分([link](https://leetcode-cn.com/problems/maximum-number-of-points-with-cost/),2021.9.29)
- 火柴拼正方形(题目编号473:[link](https://leetcode-cn.com/problems/matchsticks-to-square/),2021.9.29)
- 设计地铁系统(题目编号1396:[link](https://leetcode-cn.com/problems/design-underground-system/),2021.9.29)
- 爱吃香蕉的珂珂(题目编号875:[link](https://leetcode-cn.com/problems/koko-eating-bananas/),2021.10.6)
- 单词长度的最大乘积(题目编号:剑指Offer II 005,[link](https://leetcode-cn.com/problems/aseY1I/),2021.10.6)
- 将字符串拆成递减的连续值(题目编号:1849,[link](),2021.10.6)
- 删除最短的子数组使剩余数组有序(题目编号1574:[link](https://leetcode-cn.com/problems/shortest-subarray-to-be-removed-to-make-array-sorted/),2021.10.23)
- 所有子集(题目编号:剑指Offer II 079,[link](https://leetcode-cn.com/problems/TVdhkn/),2021.10.23)
- 到达目的地的方案数(题目编号:1976,[link](),2021.10.23)
此前已完成137道简单题目。
-
数组的度(题目编号697:link)
- 数组正序逆序遍历,找到每个数字第一次和最后一次出现的位置,统计每个元素出现的次数。
- 注意可以在单次遍历的时候就可以统计出数字的最大次数,以及符合这个次数的所有数字。
- vector没有自带find方法,但是可以使用algorithm库里的find函数,find(vector.begin(), vector.end(), target),如果找不到,返回的是vector.end()。
#include
class Solution {
public:
int findShortestSubArray(vector& nums) {
unordered_map map_pos, map_count;
for(int i = 0; i < nums.size(); i++){
if(map_pos.find(nums[i]) == map_pos.end()){
map_pos[nums[i]] = i;
map_count[nums[i]] = 1;
}
else{
map_count[nums[i]]++;
}
}
int degree = 0;
vector target_nums;
for(unordered_map::iterator it = map_count.begin(); it != map_count.end(); it++){
if(it->second == degree){
target_nums.push_back(it->first);
}
else if(it->second > degree){
target_nums.clear();
target_nums.push_back(it->first);
degree = it->second;
}
}
unordered_map map_pos_inverse;
for(int i = nums.size() - 1; i >= 0; i--){
if(find(target_nums.begin(), target_nums.end(), nums[i]) == target_nums.end()) continue;
if(map_pos_inverse.find(nums[i]) == map_pos_inverse.end()){
map_pos_inverse[nums[i]] = i;
}
}
int min_length = nums.size();
for(int i = 0; i < target_nums.size(); i++){
min_length = min(min_length, map_pos_inverse[target_nums[i]] - map_pos[target_nums[i]] + 1);
}
return min_length;
}
};
-
二叉搜索树(题目编号700:link)
/**
* 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:
TreeNode* searchBST(TreeNode* root, int val) {
if(root == NULL) return NULL;
if(root->val == val) return root;
if(root->val < val) return searchBST(root->right, val);
return searchBST(root->left, val);
}
};
-
数据流中的第K大元素(题目编号703:link)
class KthLargest {
public:
stack s1, s2;
int K = 0;
KthLargest(int k, vector& nums) {
this->K = k;
for (int i = 0; i < nums.size(); i++) {
if (s1.size() == 0) {
s1.push(nums[i]);
}
else {
this->add(nums[i]);
}
}
}
int add(int val) {
if (s1.size() == this->K) {
if (val <= s1.top()) return s1.top();
s1.pop();
}
while (s1.size() > 0 && s1.top() < val) {
s2.push(s1.top());
s1.pop();
}
s1.push(val);
while (s2.size() > 0) {
s1.push(s2.top());
s2.pop();
}
return s1.top();
}
};
- 使用multiset容器,会自动升序排序,插入元素是insert,当size()大于K时,调用erase(multiset.begin())丢弃最小的值。add函数返回的是multiset.begin()指针对应的值。
class KthLargest {
public:
multiset topK;
int K = 0;
KthLargest(int k, vector& nums) {
this->K = k;
for(int num: nums){
add(num);
}
}
int add(int val) {
this->topK.insert(val);
if(this->topK.size() > this->K){
this->topK.erase(this->topK.begin());
}
return *(this->topK).begin();
}
};
- 还可以用优先队列,greater,小顶堆。也是自动排好序的。
class KthLargest {
public:
priority_queue, greater> pq;
int K;
KthLargest(int k, vector& nums) {
K = k;
for(int i = 0; i < nums.size(); i++)
{
add(nums[i]);
}
}
int add(int val) {
pq.push(val);
if(pq.size() > K){
pq.pop();
}
return pq.top();
}
};
/**
* Your KthLargest object will be instantiated and called as such:
* KthLargest* obj = new KthLargest(k, nums);
* int param_1 = obj->add(val);
*/
-
二分查找(题目编号704:link)
class Solution {
public:
int search(vector& nums, int target) {
int left = 0, right = nums.size() - 1, mid;
while(left <= right){
mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}
if(nums[mid] < target){
left = mid + 1;
}
else{
right = mid - 1;
}
}
return -1;
}
};
-
转换成小写字母(题目编号709:link)
- 题目要求是大写字母转换成小写字母,其他字符应当保持不变吧,需要注意一下。
class Solution {
public:
string toLowerCase(string str) {
int dif_lower, dif_higher;
for(int i = 0; i < str.size(); ++i){
dif_lower = str[i] - 'a';
dif_higher = str[i] - 'A';
if(dif_lower >= 0 && dif_lower <= 26){
continue;
}
else if(dif_higher >= 0 && dif_higher <= 26){
str[i] = 'a' + dif_higher;
}
}
return str;
}
};
-
1比特与2比特字符(题目编号717:link)
- 从第0位置开始,如果是0,设置flag为true,并将指针后移1格,表示当前字符片段结束,如果是1,则设置flag为false,将指针后移2格。
class Solution {
public:
bool isOneBitCharacter(vector& bits) {
if(bits.size() == 0) return false;
bool flag;
int idx = 0;
while(idx <= bits.size() - 1){
if(bits[idx] == 0){
flag = true;
++idx;
}
else{
flag = false;
idx += 2;
}
}
return flag;
}
};
-
图像渲染(题目编号733:link)
- 使用队列遍历即可,每次弹出一个点,遍历它的上下左右四个点,复合要求的放到队尾,同时用一个visited变量记录每个点是不是已经访问过了,避免重复计算,死循环。
- 切记要把最开始给的那个点的颜色给改了。
class Solution {
public:
vector> floodFill(vector>& image, int sr, int sc, int newColor) {
vector> visited(image.size(), vector(image[0].size()));
queue> q;
vector point;
point.push_back(sr);
point.push_back(sc);
q.push(point);
int old_color = image[sr][sc], x, y;
while(q.size() > 0){
point = q.front();
q.pop();
x = point[0];
y = point[1];
check_color(x - 1, y, image, old_color, visited, q, newColor);
check_color(x + 1, y, image, old_color, visited, q, newColor);
check_color(x, y - 1, image, old_color, visited, q, newColor);
check_color(x, y + 1, image, old_color, visited, q, newColor);
}
return image;
}
bool check_coor(int x, int y, int size_x, int size_y){
if(x >= 0 && x < size_x && y >= 0 && y < size_y){
return true;
}
return false;
}
void check_color(int x, int y, vector>& image, int old_color, vector>& visited, queue>& q, int newColor){
if(check_coor(x, y, image.size(), image[0].size()) && (!visited[x][y]) && image[x][y] == old_color){
visited[x][y] = 1;
vector point;
point.push_back(x);
point.push_back(y);
q.push(point);
image[x][y] = newColor;
}
}
};
-
设计哈希集合(题目编号705:link)
- 用取余哈希建立给定数字到桶下标的映射,如果出现哈希冲突,就采用链表的形式。后面就是链表的尾插,删除,查找算法。
- 为什么说桶数量为质数时冲突会少一些?
- 注意题目说的是集合,所以不会有重复数字出现的。插入的时候要注意检查是否已经出现过。保证没有重复值,删除的时候代码只需删一次。
class MyHashSet {
private:
class Node {
public:
Node(int value){
val = value;
next = NULL;
}
int val;
Node* next;
};
public:
/** Initialize your data structure here. */
int bucket_num = 769;
vector buckets;
MyHashSet() {
buckets = vector(bucket_num, NULL);
buckets.clear();
}
int _hash(int value){
return value % bucket_num;
}
void add(int key) {
int bucket_id = _hash(key);
Node* bucket_head = buckets[bucket_id];
if(bucket_head == NULL){
buckets[bucket_id] = new Node(key);
}
else{
while(bucket_head != NULL){
if(bucket_head->val == key){
return;
}
if(bucket_head->next == NULL){
break;
}
bucket_head = bucket_head->next;
}
bucket_head->next = new Node(key);
}
}
void remove(int key) {
int bucket_id = _hash(key);
Node* bucket_head = buckets[bucket_id];
if(bucket_head != NULL){
if(bucket_head->val == key){
Node* to_delete = bucket_head;
buckets[bucket_id] = bucket_head->next;
delete to_delete;
}
else{
while(bucket_head->next != NULL && bucket_head->next->val != key){
bucket_head = bucket_head->next;
}
if(bucket_head->next != NULL){
Node* to_delete = bucket_head->next;
bucket_head->next = bucket_head->next->next;
delete to_delete;
}
}
}
}
/** Returns true if this set contains the specified element */
bool contains(int key) {
int bucket_id = _hash(key);
Node* bucket_head = buckets[bucket_id];
if(bucket_head == NULL){
return false;
}
else{
while(bucket_head != NULL){
if(bucket_head->val == key){
return true;
}
bucket_head = bucket_head->next;
}
return false;
}
}
};
/**
* Your MyHashSet object will be instantiated and called as such:
* MyHashSet* obj = new MyHashSet();
* obj->add(key);
* obj->remove(key);
* bool param_3 = obj->contains(key);
*/
-
建立哈希映射(题目编号706:link)
- 这题就是把key: value分别用上一道题目的哈希表存储,还可以把Node设计成可以同时保存key和value,写代码的时候写的是key和value分开存储,后来想想还是把Node的设计改一下更方便。
- 注意添加数据时,遇到已经出现的key值时是需要更新value。
class MyHashMap {
private:
class Node {
public:
Node(int value){
val = value;
next = NULL;
}
int val;
Node* next;
};
public:
/** Initialize your data structure here. */
int bucket_num = 769;
vector buckets;
vector buckets_value;
MyHashMap() {
buckets = vector(bucket_num, NULL);
buckets_value = vector(bucket_num, NULL);
}
int _hash(int value){
return value % bucket_num;
}
/** value will always be non-negative. */
void put(int key, int value) {
int bucket_id = _hash(key);
Node* bucket_head = buckets[bucket_id];
Node* bucket_value_head = buckets_value[bucket_id];
if(bucket_head == NULL){
buckets[bucket_id] = new Node(key);
buckets_value[bucket_id] = new Node(value);
}
else{
while(bucket_head != NULL){
if(bucket_head->val == key){
bucket_value_head->val = value;
return;
}
if(bucket_head->next == NULL){
break;
}
bucket_head = bucket_head->next;
bucket_value_head = bucket_value_head->next;
}
bucket_head->next = new Node(key);
bucket_value_head->next = new Node(value);
}
}
/** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
int get(int key) {
int bucket_id = _hash(key);
Node* bucket_head = buckets[bucket_id];
Node* bucket_value_head = buckets_value[bucket_id];
if(bucket_head == NULL){
return -1;
}
else{
while(bucket_head != NULL){
if(bucket_head->val == key){
return bucket_value_head->val;
}
bucket_head = bucket_head->next;
bucket_value_head = bucket_value_head->next;
}
return -1;
}
}
/** Removes the mapping of the specified value key if this map contains a mapping for the key */
void remove(int key) {
int bucket_id = _hash(key);
Node* bucket_head = buckets[bucket_id];
Node* bucket_value_head = buckets_value[bucket_id];
if(bucket_head != NULL){
if(bucket_head->val == key){
Node* to_delete = bucket_head;
buckets[bucket_id] = bucket_head->next;
delete to_delete;
to_delete = bucket_value_head;
buckets_value[bucket_id] = bucket_value_head->next;
delete to_delete;
}
else{
while(bucket_head->next != NULL && bucket_head->next->val != key){
bucket_head = bucket_head->next;
bucket_value_head = bucket_value_head->next;
}
if(bucket_head->next != NULL){
Node* to_delete = bucket_head->next;
bucket_head->next = bucket_head->next->next;
delete to_delete;
to_delete = bucket_value_head->next;
bucket_value_head->next = bucket_value_head->next->next;
delete to_delete;
}
}
}
}
};
/**
* Your MyHashMap object will be instantiated and called as such:
* MyHashMap* obj = new MyHashMap();
* obj->put(key,value);
* int param_2 = obj->get(key);
* obj->remove(key);
*/
-
翻转字符串里的单词(题目编号151:link)
- 先局部每个单词内部翻转,然后全部再翻转一次。
- 需要注意的是空格隔开的单词。
- 原地算法,需要拷贝新找到的字符串到紧跟着前一个单词的位置,以跳过空格。
- 注意最后还需要判断是否还有一个单词没翻转。
class Solution {
public:
string reverseWords(string s) {
int start = -1, len = 0;
string res;
for(int i = 0; i < s.size(); ++i){
if(start == -1){
if(s[i] != ' '){
start = i;
}
}
else{
if(s[i] == ' '){
reverse(s, start, i - 1);
copy(s, start, i - start, len);
len += i - start;
s[len] = ' ';
++len;
start = -1;
}
}
}
if(start != -1){
reverse(s, start, s.size() - 1);
copy(s, start, s.size() - start, len);
len += s.size() - start;
}
else{
--len;
}
reverse(s, 0, len - 1);
return s.substr(0, len);
}
void reverse(string& s, int i, int j){
while(i < j){
char c = s[i];
s[i] = s[j];
s[j] = c;
++i;
--j;
}
}
void copy(string& s, int src, int len, int dest){
if(src == dest){
return;
}
while(len > 0){
s[dest] = s[src];
++dest;
++src;
--len;
}
}
};
-
面试题 16.07. 最大数值(link)
- 不用if-else和比较运算符比较a和b的大小,利用int类型正数右移31位为0,负数右移31位为-1的性质来表示a和b的大小关系。由于相减可能导致溢出,故先转为long型,右移63位。
class Solution {
public:
int maximum(int a, int b) {
long c = a, d = b, k = 1 + ((c - d) >> 63);
return k * a + !k * b;
}
};
-
在线选举(题目编号911:link)
- 记录每次leader变动时的获胜者和时间,在查询的时候进行二分查找。
- 注意更新获胜者票数和更新获胜者的条件不完全一样。
class TopVotedCandidate {
public:
vector> result;
TopVotedCandidate(vector& persons, vector& times) {
unordered_map mp;
int count_of_winner = 0, winner = -1;
for(int i = 0; i < persons.size(); ++i){
++mp[persons[i]];
if(mp[persons[i]] >= count_of_winner){
if(persons[i] != winner){
result.push_back(vector{persons[i], times[i]});
}
count_of_winner = mp[persons[i]];
winner = persons[i];
}
}
}
int q(int t) {
if(result.size() == 0) return -1;
int lo = 1, hi = result.size();
while(lo < hi){
int mid = lo + (hi - lo) / 2;
if(result[mid][1] <= t){
lo = mid + 1;
}
else{
hi = mid;
}
}
return result[lo - 1][0];
}
};
/**
* Your TopVotedCandidate object will be instantiated and called as such:
* TopVotedCandidate* obj = new TopVotedCandidate(persons, times);
* int param_1 = obj->q(t);
*/
-
前 K 个高频元素(题目编号347:link)
- 之前做过第K大的数,跟这个很类似。
- 这里尝试一下堆的做法。
- 利用优先队列,需要好好学习一下这个堆。
- 这次是参考题解写的。
- 注意cmp函数必须是static的,之前也遇到过这个问题,会导致编译不通过。
class Solution {
public:
static bool cmp(pair p, pair q){
return p.second > q.second;
}
vector topKFrequent(vector& nums, int k) {
unordered_map mp;
for(int n: nums){
mp[n]++;
}
priority_queue, vector>, decltype(&cmp)> q(cmp);
for(auto& [num, count]: mp){
if(q.size() == k){
if(q.top().second < count){
q.pop();
q.emplace(num, count);
}
}
else{
q.emplace(num, count);
}
}
vector res;
while(!q.empty()){
res.emplace_back(q.top().first);
q.pop();
}
return res;
}
};
-
形成两个异或相等数组的三元组数目(题目编号1442:link)
- 题解讲得很好,利用异或运算的性质,来减少计算量,降低计算量到O(n^2)和O(n)。
class Solution {
public:
int countTriplets(vector& arr) {
int ans = 0;
for(int i = 0; i < arr.size() - 1; ++i){
int x = arr[i];
for(int k = i + 1; k < arr.size(); ++k){
x ^= arr[k];
if(x == 0){
ans += k - i;
}
}
}
return ans;
}
};
class Solution {
public:
int countTriplets(vector& arr) {
unordered_map cnt = {{0, 1}}, indsum = {{0, 0}};
int x = 0, ans = 0;
for(int k = 0; k < arr.size(); ++k){
x ^= arr[k];
if(cnt[x] > 0){
ans += cnt[x] * k - indsum[x];
}
++cnt[x];
indsum[x] += (k + 1);
}
return ans;
}
};
-
最大数(题目编号179:link)
- 思路很简单,自定义比较函数,数字两两比较谁放在前边组成的数字大。
- 非常需要注意的是:比较函数里得是return s1>s2,如果写成s1>=s2,就会报heap overflow,堆溢出错误。
class Solution {
public:
string largestNumber(vector& nums) {
if(nums.size() == 0) return "0";
sort(nums.begin(), nums.end(), cmp);
string ans;
if(nums[0] == 0) return "0";
for(int n: nums){
ans += to_string(n);
}
return ans;
}
static int cmp(int n1, int n2){
string sa = to_string(n1);
string sb = to_string(n2);
string s1 = sa + sb;
string s2 = sb + sa;
return s1 > s2;
}
};
-
不同路径(题目编号62:link)
- 这题本来想的是直接采用组合数C_{m+n-2}^{m-1},但好像不是这样子的。
- 使用递归或者动态规划,递推公式,到(i, j)点只有从(i-1,j)和(i,j-1)两个位置,那么到达(i,j)的方法数就等于到达这两个前序点的方法数之和。
- 递归超时,改为使用动态规划。
class Solution {
public:
int uniquePaths(int m, int n) {
vector> f(m, vector(n));
f[0][0] = 1;
for(int i = 1; i < m; ++i){
f[i][0] = 1;
}
for(int i = 1; i < n; ++i){
f[0][i] = 1;
}
for(int i = 1; i < m; ++i){
for(int j = 1; j < n; ++j){
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
return f[m - 1][n - 1];
// return func(m, n);
}
int func(int m, int n){
if(m == 1 && n == 1){
return 1;
}
if(m == 1 || n == 1){
return 1;
}
return func(m - 1, n) + func(m, n - 1);
}
};
-
不同路径II(题目编号63:link)
- 2021.3.24
- 这题相当于上题中某些位置由于是阻碍,导致到达该点的方法是必定为0。
- 注意再第一行和第一列,当该点不是阻碍时,只要前面有一个点是阻碍,那么该点都是无法达到的,这点要尤其注意。还可以提前跳出循环。
- 注意测试用例中有变态的,(0,0)起点位置是阻碍的,既然考虑到起点可能是阻碍,那么终点也考虑一下。如果起点或终点是阻碍,那么直接返回结果0。
class Solution {
public:
int uniquePathsWithObstacles(vector>& obstacleGrid) {
int m = obstacleGrid.size(), n = obstacleGrid[0].size();
vector> f(m, vector(n));
if(obstacleGrid[0][0] == 1 || obstacleGrid[m - 1][n - 1] == 1){
return 0;
}
f[0][0] = 1;
for(int i = 1; i < m; ++i){
if(obstacleGrid[i][0] == 1){
break;
}
f[i][0] = 1;
}
for(int i = 1; i < n; ++i){
if(obstacleGrid[0][i] == 1){
break;
}
f[0][i] = 1;
}
for(int i = 1; i < m; ++i){
for(int j = 1; j < n; ++j){
if(obstacleGrid[i][j] == 0){
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
}
return f[m - 1][n - 1];
}
};
-
组合(题目编号77:link)
- 2种解法,一种是递推,利用C_{n-1}{k}和C_{n}{k-1}(每组结果再加上一个数字n)进行合并得到,耗费内存比较多,且有重复计算的问题。另一种就是DFS搜索,使用visited数组,这次一次就写对了,也不需要调试修改代码,有进步~
- 下面代码段注释掉的就是第一种解法
class Solution {
public:
vector> combine(int n, int k) {
if(k < 0 || k > n){
return vector>{};
}
if(n == k){
vector ans = vector(n);
for(int i = 0; i < n; ++i){
ans[i] = i + 1;
}
return vector>{ans};
}
// vector> ans = combine(n - 1, k);
// vector> ans2 = combine(n - 1, k - 1);
// for(int i = 0; i < ans2.size(); ++i){
// vector tmp = ans2[i];
// tmp.push_back(n);
// ans.push_back(tmp);
// }
// return ans;
vector> res;
vector visited(n);
selection(n, k, 0, 0, visited, res);
return res;
}
void selection(int& n, int& k, int used, int cur_idx, vector& visited, vector>& res){
if(used == k){
vector tmp(k);
for(int i = 0; i < visited.size() && used > 0; ++i){
if(visited[i]){
tmp[k - used] = i + 1;
--used;
}
}
res.push_back(tmp);
return;
}
if(n - cur_idx < k - used){
return;
}
visited[cur_idx] = 1;
++used;
selection(n, k, used, cur_idx + 1, visited, res);
visited[cur_idx] = 0;
--used;
selection(n, k, used, cur_idx + 1, visited, res);
}
};
-
单词搜索(题目编号79:link)
class Solution {
public:
bool exist(vector>& board, string word) {
int rows = board.size(), cols = board[0].size();
if(rows * cols < word.size()){
return false;
}
vector cnt_arr(52);
for(int i = 0; i < rows; ++i){
for(int j = 0; j < cols; ++j){
if(board[i][j] >= 'a' && board[i][j] <= 'z'){
cnt_arr[]
}
}
}
return search(board, word, vector>(rows, vector(cols)), 0, 0, 0, rows, cols);
}
bool search(vector>& board, string& word, vector> visited, int cur_idx, int cur_row, int cur_col, int rows, int cols){
if(cur_idx == word.size()){
return true;
}
if(cur_row < 0 || cur_row >= rows || cur_col < 0 || cur_col >= cols){
return false;
}
if(cur_idx == 0){
for(int i = 0; i < rows; ++i){
for(int j = 0; j < cols; ++j){
if(board[i][j] == word[0]){
visited[i][j] = 1;
bool ans = search(board, word, visited, 1, i+1, j, rows, cols) || search(board, word, visited, 1, i-1, j, rows, cols) || search(board, word, visited, 1, i, j+1, rows, cols) || search(board, word, visited, 1, i, j-1, rows, cols);
if(ans){
return ans;
}
visited[i][j] = 0;
}
}
}
}
else{
if(visited[cur_row][cur_col] || board[cur_row][cur_col] != word[cur_idx]){
return false;
}
visited[cur_row][cur_col] = 1;
int i = cur_row, j = cur_col;
return search(board, word, visited, cur_idx + 1, i+1, j, rows, cols) || search(board, word, visited, cur_idx + 1, i-1, j, rows, cols) || search(board, word, visited, cur_idx + 1, i, j+1, rows, cols) || search(board, word, visited, cur_idx + 1, i, j-1, rows, cols);
}
return false;
}
};
-
找到K个最接近的元素(题目编号658:link)
- 最开始美看到数组是有序的,就想到用大顶堆和小顶堆来做。用大顶堆维护k个最接近大于等于目标值x的元素,再用小顶堆维护k个最接近且小于等于目标值x的元素。最后再合并两个堆,得到k个最接近的。
- 又考虑能否只使用一个堆,发现优先队列priority_queue可以使用自定义的数据结构和排序方法,但是和sort函数不太一样,排序函数得到一个数据类型。
priority,vector>, cmp> q;
struct cmp{
bool operator()(pair a, pair b){
return a.second > b.second;
}
}
- 又学到一种。可以用pair记录元素和元素到目标值x的距离,维护k个最接近的元素。
class Solution {
public:
vector findClosestElements(vector& arr, int k, int x) {
// priority_queue max_heap;
// priority_queue, greater> min_heap;
// for(int n: arr){
// if(n >= x){
// max_heap.push(n);
// if(max_heap.size() > k){
// max_heap.pop();
// }
// }
// else{
// min_heap.push(n);
// if(min_heap.size() > k){
// min_heap.pop();
// }
// }
// }
priority_queue, vector>, cmp1> q;
for(int n: arr){
q.push(pair(n, abs(n - x)));
if(q.size() > k){
q.pop();
}
}
vector ans1, ans2;
while(!q.empty()){
auto c = q.top();
if(c.first >= x){
ans1.push_back(c.first);
}
else{
ans2.push_back(c.first);
}
q.pop();
}
vector ans;
for(int i = 0; i < ans2.size(); ++i){
ans.push_back(ans2[i]);
}
for(int i = ans1.size() - 1; i >= 0; --i){
ans.push_back(ans1[i]);
}
return ans;
}
static bool cmp(pair a, pair b){
return a.second > b.second;
}
struct cmp1{
bool operator()(pair a, pair b){
if(a.second == b.second){
return a.first <= b.first;
}
else{
return a.second < b.second;
}
}
};
};
- 后来发现数组是有序的,那就可以用二分搜索的方法找到距离x最接近的一个元素,然后向两边搜索。充分利用数组的性质。
#include
class Solution {
public:
vector findClosestElements(vector& arr, int k, int x) {
int pos = bsearch(arr, x);
pos = min(pos, int(arr.size() - 1));
vector ans1, ans2;
if(pos + 1 < arr.size() && abs(arr[pos + 1] - x) < abs(arr[pos] - x)){
ans1.push_back(arr[pos + 1]);
++pos;
}
else if(pos - 1 >= 0 && abs(arr[pos - 1] - x) <= abs(arr[pos] - x)){
ans2.push_back(arr[pos - 1]);
--pos;
}
else{
ans2.push_back(arr[pos]);
}
int lo = pos - 1, hi = pos + 1;
while(ans1.size() + ans2.size() < k && (lo >= 0 || hi < arr.size())){
if(lo >= 0 && hi < arr.size()){
if(abs(x - arr[lo]) <= abs(arr[hi] - x)){
ans2.push_back(arr[lo]);
--lo;
}
else{
ans1.push_back(arr[hi]);
++hi;
}
}
else if(lo >= 0){
ans2.push_back(arr[lo]);
--lo;
}
else{
ans1.push_back(arr[hi]);
++hi;
}
}
vector ans;
for(int i = ans2.size() - 1; i >= 0; --i){
ans.push_back(ans2[i]);
}
for(int i = 0; i < ans1.size(); ++i){
ans.push_back(ans1[i]);
}
return ans;
}
int bsearch(vector arr, int target){
int lo = 0, hi = arr.size() - 1;
while(lo <= hi){
int mid = lo + (hi - lo) / 2;
if(arr[mid] == target){
return mid;
}
else if(arr[mid] < target){
lo = mid + 1;
}
else{
hi = mid - 1;
}
}
return lo;
}
};
-
目标和(题目编号494:link,2021.7.7)
- 本来以为是搜索空间压缩就可以解决问题,最后发现还是时间复杂度太高,查阅答案发现是需要动态规划思想。
- 统计数组和sum,正样本由于数组全是非负整数,假设标记为负数的所有元素绝对值之和是neg,那么sum-neg是剩余数字之和,sum-neg-neg = target,所以target+sum>=0,sum-target是非负偶数。也可以定义标记为正数的所有元素绝对值之和是pos,那么sum-pos是剩余数字之和,pos-(sum-pos) = target,所以target+sum>=0,sum+target是非负偶数。
- 接着就是判断数组从0到i处这i+1个元素中有多少个组合之和是pos或者neg。
class Solution {
public:
int findTargetSumWays(vector& nums, int target) {
int sum = 0;
for(int i = 0; i < nums.size(); ++i){
sum += nums[i];
}
if(target + sum < 0 || ((target + sum) & 1) == 1) return 0;
int T = (sum + target) >> 1;
vector pre = vector(T + 1);
pre[0] = 1;
for(int i = 1; i <= nums.size(); ++i){
vector cur(T + 1);
for(int j = 0; j <= T; ++j){
cur[j] = pre[j];
if(j - nums[i - 1] >= 0){
cur[j] += pre[j - nums[i - 1]];
}
}
pre = cur;
}
return pre[T];
}
};
-
随机数索引(题目编号398:link,2021.7.7)
- 一种新的采样算法,在数据流处理中保证每个数被采样的概率都是相等的。
- 如果当前位置以前,目标数字出现了cnt次,生成一个[1, cnt]的随机数,如果等于cnt,那么替换原有结果。那么到最后目标数字每个位置被采样的概率都是一样的。
class Solution {
private:
vector nums;
public:
Solution(vector& nums) {
this->nums = nums;
}
int pick(int target) {
int cnt = 0, ind = -1;
for(int i = 0; i < this->nums.size(); ++i){
if(nums[i] == target){
++cnt;
if(rand() % cnt == 0){
ind = i;
}
}
}
return ind;
}
};
/**
* Your Solution object will be instantiated and called as such:
* Solution* obj = new Solution(nums);
* int param_1 = obj->pick(target);
*/
-
两数相加II(题目编号445:link)
- 最容易想到的是链表逆序,相加,然后再逆序过来。之前看别人代码看到一种把两个链表等长不等长在一个while循环里全都考虑的代码,这次直接写,写出来了。
- 注意a?b:c+d?e:0,这种写法,会把c+d?e:0整体看成三元运算符的最后一项,需要加括号。
/**
* 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* addTwoNumbers(ListNode* l1, ListNode* l2) {
if(!l1){
return l2;
}
if(!l2){
return l1;
}
ListNode* r1 = reverseLinkList(l1);
ListNode* r1_bak = r1;
ListNode* r2 = reverseLinkList(l2);
int carry = 0;
ListNode* last;
while(r1 || r2){
int res = (r1? r1->val: 0) + (r2? r2->val:0) + carry;
if(r1){
r1->val = res % 10;
}
else{
last->next = new ListNode(res % 10);
r1 = last->next;
}
carry = res / 10;
last = r1;
if(r1) r1 = r1->next;
if(r2) r2 = r2->next;
}
if(carry){
last->next = new ListNode(carry);
}
return reverseLinkList(r1_bak);
}
ListNode* reverseLinkList(ListNode* head){
if(!head) return head;
ListNode *pre = nullptr, *cur = head, *next;
while(cur){
next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
};
-
移动所有球到每个盒子所需的最小操作数(题目编号1769:link)
- 关键是找到规律,统计左侧/右侧所有小球移动到当前位置的花费,到达下一个位置利用递关系快速计算。到下一个位置时,左侧/右侧所有小球都需要额外的一步来到达当前位置。
class Solution {
public:
vector minOperations(string boxes) {
int presum, precost, n = boxes.size();
vector ans = vector(n), lsum = vector(n), rsum = vector(n), lcost = vector(n), rcost = vector(n);
for(int i = 0; i < n; ++i){
presum = (i == 0) ? 0: lsum[i - 1];
precost = (i <= 1) ? 0: lcost[i - 1];
if(i > 0 && boxes[i - 1] == '1'){
lsum[i] = presum + 1;
}
else{
lsum[i] = presum;
}
lcost[i] = precost + lsum[i];
ans[i] += lcost[i];
// reverse
presum = (i == 0) ? 0: rsum[n - 1 - (i - 1)];
precost = (i <= 1) ? 0: rcost[n - 1 - (i - 1)];
if(i > 0 && boxes[n - 1 - (i - 1)] == '1'){
rsum[n - 1 - i] = presum + 1;
}
else{
rsum[n - 1 - i] = presum;
}
rcost[n - 1 - i] = precost + rsum[n - 1 - i];
ans[n - 1 - i] += rcost[n - 1 - i];
}
return ans;
}
};
-
字母板上的路径(题目编号1138:link)
- 题目难点主要是字母z,如果上一个字母是z,那么一定要先垂直移动,再水平移动,其他情况都先水平移动,再垂直移动(已经包括了下一个字母是z的情况)。
class Solution {
public:
string alphabetBoardPath(string target) {
if(target.size() == 0) return "";
vector alphabet = {
"abcde", "fghij", "klmno", "pqrst", "uvwxy", "z"
};
string ans;
find_next(target, ans, 0, 0, 0);
return ans;
}
void find_next(string target, string& ans, int ind, int x, int y){
if(ind == target.size()) return;
int sx = int(target[ind] - 'a') / 5, sy = int(target[ind] - 'a') % 5;
string vertical = (sx >= x)? "D":"U", horizontal = (sy >= y)? "R":"L";
if(x == 5){
append_char(ans, abs(sx - x), vertical);
append_char(ans, abs(sy - y), horizontal);
}
else{
append_char(ans, abs(sy - y), horizontal);
append_char(ans, abs(sx - x), vertical);
}
ans += "!";
find_next(target, ans, ind + 1, sx, sy);
}
void append_char(string& ans, int num, string s){
for(int i = 0; i < num; ++i){
ans += s;
}
}
};
-
索引处的解码字符串(题目编号880:link)
-
安排电影院座位(题目编号1386:link)
- 最重要的是用map保留有人占位的排,剩下的没有人的是不需要存储的,还有用8位的bitmask来记录2-9号座位哪些位置被占用。如果用多个数组来记录的话会超时。
class Solution {
public:
int maxNumberOfFamilies(int n, vector>& reservedSeats) {
unordered_map mp;
for(auto v: reservedSeats){
if(v[1] == 1 || v[1] == 10) continue;
mp[v[0]] |= (1 << (v[1] - 2));
}
int ans = 2 * (n - mp.size()), left = 0b00001111, middle = 0b11000011, right = 0b11110000;
for(unordered_map::iterator it = mp.begin(); it != mp.end(); ++it){
if((it->second | left) == left || (it->second | middle) == middle || (it->second | right) == right){
++ans;
}
}
return ans;
}
};
-
统计二叉树中好节点的数目(题目编号1448:link)
- 递归,统计从根节点到该节点的历史最大值,跟当前节点的值比较即可。
/**
* 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:
int goodNodes(TreeNode* root) {
int ans = 0;
midTraverse(root, ans, INT_MIN);
return ans;
}
void midTraverse(TreeNode* root, int& ans, int maxVal){
if(!root) return;
if(root->val >= maxVal) ++ans;
maxVal = max(maxVal, root->val);
midTraverse(root->left, ans, maxVal);
midTraverse(root->right, ans, maxVal);
}
};
-
飞机座分配概率(题目编号1227:link)
class Solution {
public:
double nthPersonGetsNthSeat(int n) {
return n == 1 ? 1:0.5;
}
};
-
连续的子数组和(题目编号523:link)
- 利用前缀和快速计算子数组和,由于这里只需要判断子数组和是否是k的倍数,所以前缀和只需要保留除k后的余数。前缀和数组中两个位置相等,就表示区间内子数组和是k的倍数,接下来还需要判断区间长度。
- 此外,还需要注意初始条件mp[0] = -1,还有不能直接赋值mp[nums[0] % k] = 0,因为nums[0]可能刚好是k的倍数,就把mp[0] = -1覆盖掉了。
class Solution {
public:
bool checkSubarraySum(vector& nums, int k) {
int n = nums.size();
if(n <= 1) return false;
vector prefixSum(n);
prefixSum[0] = nums[0] % k;
unordered_map mp;
mp[0] = -1;
int before = 0;
for(int i = 0; i < n; ++i){
prefixSum[i] = (before + nums[i]) % k;
if(mp.find(prefixSum[i]) != mp.end()){
if(i - mp[prefixSum[i]] >= 2) return true;
}
else{
mp[prefixSum[i]] = i;
}
before = prefixSum[i];
}
return false;
}
};
-
整数拆分(题目编号343:link)
- 本题需要使用动态规划,dp[i]表示对i拆分的最大乘积。在对i进行拆分时,依次考虑提取出1,2,3,…,i-1,剩下的部分可以选择拆或者不拆,乘积分别是i-j和dp[i-j]。
- 还有更复杂一些的数学推导,可以简化计算量。
class Solution {
public:
int integerBreak(int n) {
vector dp(n + 1);
for(int i = 2; i <= n; ++i){
for(int j = 1; j < i; ++j){
dp[i] = max(dp[i], j * max(i - j, dp[i - j]));
}
}
return dp[n];
}
};
-
最少侧跳次数(题目编号1824:link,2021.7.28)
- 动态规划
- 跟自己的计算方式略有差异,自己的是错的。抽空再分析一下。
class Solution {
public:
int minSideJumps(vector& obstacles) {
int n = obstacles.size();
if(n <= 1) return 0;
vector> dp(n, vector(3, 0x3fffffff));
dp[0][0] = dp[0][2] = 1;
dp[0][1] = 0;
for(int i = 1; i < n; ++i){
if(obstacles[i] != 1) dp[i][0] = dp[i - 1][0];
if(obstacles[i] != 2) dp[i][1] = dp[i - 1][1];
if(obstacles[i] != 3) dp[i][2] = dp[i - 1][2];
if(obstacles[i] != 1) dp[i][0] = min(dp[i][0], min(dp[i][1], dp[i][2]) + 1);
if(obstacles[i] != 2) dp[i][1] = min(dp[i][1], min(dp[i][0], dp[i][2]) + 1);
if(obstacles[i] != 3) dp[i][2] = min(dp[i][2], min(dp[i][0], dp[i][1]) + 1);
}
return min(dp[n-1][0], min(dp[n-1][1], dp[n-1][2]));
}
};
-
重新安排行程(题目编号332:link,2021.7.27)
- Hierholzer 算法用于在连通图中寻找欧拉路径,其流程如下:从起点出发,进行深度优先搜索。每次沿着某条边从某个顶点移动到另外一个顶点的时候,都需要删除这条边。如果没有可移动的路径,则将所在节点加入到栈中,并返回。
- 还不懂
class Solution {
private:
unordered_map, std::greater>> vec;
vector stk;
public:
void dfs(const string& curr){
while(vec.count(curr) && vec[curr].size() > 0){
string tmp = vec[curr].top();
vec[curr].pop();
dfs(move(tmp));
}
stk.emplace_back(curr);
}
vector findItinerary(vector>& tickets) {
for(auto& it: tickets){
vec[it[0]].emplace(it[1]);
}
dfs("JFK");
reverse(stk.begin(), stk.end());
return stk;
}
};
-
将二叉搜索树变平衡(题目编号1382:link,2021.7.28)
- 官方题解使用的是利用数组重建构建一棵平衡树的方法。
- 实际上构造出来的平衡树应该是不唯一的。
/**
* 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 {
private:
vector vec;
public:
void midTraverse(TreeNode* root){
if(!root) return;
if(root->left){
midTraverse(root->left);
}
vec.emplace_back(root->val);
if(root->right){
midTraverse(root->right);
}
}
TreeNode* buildTree(vector& vec, int left, int right){
if(left > right){
return nullptr;
}
int mid = left + (right - left) / 2;
TreeNode* root = new TreeNode(vec[mid]);
root->left = buildTree(vec, left, mid - 1);
root->right = buildTree(vec, mid + 1, right);
return root;
}
TreeNode* balanceBST(TreeNode* root) {
midTraverse(root);
return buildTree(vec, 0, vec.size() - 1);
}
};
-
课程表IV(题目编号1462:link,2021.7.28)
- 这个问题就是深度优先搜索,但是需要在遍历过程中记录每次查询的先序课程结果,不只是query中的,还有每一次递归查询中的先序课程。加速程序运行。
class Solution {
public:
vector checkIfPrerequisite(int numCourses, vector>& prerequisites, vector>& queries) {
unordered_map> ump;
for(auto& p: prerequisites){
ump[p[1]].emplace_back(p[0]);
}
map, bool> mem;
vector res(queries.size(), false);
for(int i = 0; i < queries.size(); ++i){
auto& q = queries[i];
res[i] = dfs(ump, mem, q[1], q[0]);
}
return res;
}
bool dfs(unordered_map>& ump, map, bool>& mem, int q, int target){
if(mem.find(pair{target, q}) != mem.end()){
return mem[pair{target, q}];
}
bool ans = false;
if(ump.find(q) == ump.end()){
ans = false;
}
else{
auto& vec = ump[q];
if(find(vec.begin(), vec.end(), target) != vec.end()){
ans = true;
}
else{
for(auto& n: vec){
if(dfs(ump, mem, n, target)){
ans = true;
break;
}
}
}
}
mem[pair{target, q}] = ans;
return ans;
}
};
-
具有所有最深节点的最小子树(题目编号865:link,2021.7.28)
- 两次深度优先搜索,第一次统计每个节点的深度。第二个判断当前节点左右孩子包含最深节点的情况,来决定返回当前节点还是某个孩子节点。
- 还有其他的解法,不太好理解。
/**
* 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 {
private:
map depth;
int maxDepth = 0;
public:
void getDepth(TreeNode* root, int d){
if(!root) return;
depth[root] = d;
maxDepth = max(maxDepth, d);
getDepth(root->left, d + 1);
getDepth(root->right, d + 1);
}
TreeNode* answer(TreeNode* root){
if(!root) return nullptr;
if(depth[root] == maxDepth) return root;
TreeNode *left = answer(root->left), *right = answer(root->right);
if(left && right) return root;
if(left) return left;
if(right) return right;
return nullptr;
}
TreeNode* subtreeWithAllDeepest(TreeNode* root) {
getDepth(root, 0);
return answer(root);
}
};
-
两个字符串的删除操作(题目编号583:link,2021.7.28)
class Solution {
private:
map, int> mp;
public:
int minDistance(string word1, string word2) {
return word1.size() + word2.size() - 2 * getLCS(word1, word2, word1.size(), word2.size());
}
int getLCS(string& word1, string& word2, int len1, int len2){
int ans = 0;
if(len1 == 0 || len2 == 0) ans = 0;
else if(mp[pair{len1, len2}] > 0){
ans = mp[pair{len1, len2}];
}
else if(word1[len1 - 1] == word2[len2 - 1]){
ans = getLCS(word1, word2, len1 - 1, len2 - 1) + 1;
}
else{
ans = max(getLCS(word1, word2, len1, len2 - 1), getLCS(word1, word2, len1 - 1, len2));
}
mp[pair{len1, len2}] = ans;
return ans;
}
};
- 动态规划法,和递归法思想是一样的,但是比递归法快很多。
class Solution {
public:
int minDistance(string word1, string word2) {
int n1 = word1.size(), n2 = word2.size();
vector> dp(n1 + 1, vector(n2 + 1, 0));
for(int i = 1; i <= n1; ++i){
for(int j = 1; j <= n2; ++j){
if(word1[i - 1] == word2[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return n1 + n2 - 2 * dp[n1][n2];
}
};
- 动态规划法2,直接令dp[i][j]表示word1前i个位置和word2前j个位置需要的最小删除次数。最后返回dp[n1][n2]。
class Solution {
public:
int minDistance(string word1, string word2) {
int n1 = word1.size(), n2 = word2.size();
vector> dp(n1 + 1, vector(n2 + 1, 0));
for(int i = 0; i <= n1; ++i){
for(int j = 0; j <= n2; ++j){
if(i == 0 || j == 0){
dp[i][j] = i + j;
continue;
}
if(word1[i - 1] == word2[j - 1]){
dp[i][j] = dp[i - 1][j - 1];
}
else{
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1;
}
}
}
return dp[n1][n2];
}
};
-
剑指Offer II020 回文字符串的个数(链接:link,2021.8.22)
- 核心思想将每个位置都视为回文字符串的中心,向两边扩展。注意有可能没有中心字符,即i和i+1作为中心。
class Solution {
public:
int countSubstrings(string s) {
int n = s.size(), ans = 0;
for(int i = 0; i < n; ++i){
ans += count(s, n, i, i);
ans += count(s, n, i, i + 1);
}
return ans;
}
int count(string& s, int n, int i, int j){
int cnt = 0;
while(i >= 0 && j < n && s[i] == s[j]){
++cnt;
--i;
++j;
}
return cnt;
}
};
-
剑指Offer II没有重复元素集合的全排列(题目编号083,链接:link)
- 回溯法,深度优先搜索。但是时间效率比较低,可能还有更高效的剪枝或者其他方法。
class Solution {
public:
vector> permute(vector& nums) {
int len = nums.size();
vector cur;
vector visited(len, false);
vector> ans;
dfs(nums, len, 0, cur, visited, ans);
return ans;
}
void dfs(vector& nums, int len, int cur_ind, vector cur, vector visited, vector>& ans){
if(cur_ind == len){
ans.push_back(cur);
return;
}
++cur_ind;
for(int i = 0; i < len; ++i){
if(!visited[i]){
visited[i] = true;
cur.push_back(nums[i]);
dfs(nums, len, cur_ind, cur, visited, ans);
visited[i] = false;
cur.pop_back();
}
}
}
};
-
多次搜索(题目编号:面试题17.17,link,2021.8.22)
- 采取了简单的匹配策略,先统计big字符串中a~z出现的位置,然后遍历small。这样存储占用空间还好,但时间效率比较低。
- 还有对small建立前缀树的方法
class Solution {
public:
vector> multiSearch(string big, vector& smalls) {
int n = big.size();
vector> char_pos(26);
for(int i = 0; i < n; ++i){
char_pos[big[i] - 'a'].push_back(i);
}
int m = smalls.size();
vector> ans;
for(int i = 0; i < m; ++i){
int cur_size = smalls[i].size();
vector cur_ans;
if(cur_size > 0){
for(auto pos: char_pos[smalls[i][0] - 'a']){
bool match = true;
if(pos + cur_size > n){
continue;
}
for(int j = pos; j < pos + cur_size; ++j){
if(big[j] != smalls[i][j - pos]){
match = false;
break;
}
}
if(match){
cur_ans.push_back(pos);
}
}
}
ans.push_back(cur_ans);
}
return ans;
}
};
class Solution {
public:
static const int N = 100000 + 10;
int childs[N][26];
int values[N];
int nodes_cnt;
vector> multiSearch(string big, vector& smalls) {
vector> ans(smalls.size());
memset(childs, 0, sizeof(childs));
memset(values, 0xff, sizeof(values));
nodes_cnt = 0;
for(int i = 0; i < smalls.size(); ++i){
insert(smalls[i], i);
}
for(int i = 0; i < big.size(); ++i){
find(big, i, ans);
}
return ans;
}
void insert(const string& s, int index_of_s){
int r = 0;
for(int i = 0; i < s.size(); ++i){
int idx = s[i] - 'a';
int& next = childs[r][idx];
if(next == 0){
next = ++nodes_cnt;
}
r = next;
}
values[r] = index_of_s;
}
void find(const string& t, int beg, vector>& ans){
int r = 0;
for(int j = beg; j < t.size(); ++j){
int idx = t[j] - 'a';
const int& next = childs[r][idx];
if(next == 0){
return;
}
if(values[next] >= 0){
ans[values[next]].push_back(beg);
}
r = next;
}
}
};
-
堆盘子(题目编号:面试题03.03,link)
- 注意vector为空和某个stack变空之和需要erase。本题没有要求调用popAt,从中间位置弹出元素时重新调整各个stack,但代码也写好了,运行过没有报错,但不确定是否一定符合要求,简单验证了一下,是对的。输入:
["StackOfPlates", "push", "push", "push", "push", "push", "push", "popAt", "popAt", "popAt"]
[[2], [1], [2], [3], [4], [5], [6], [0], [0], [1]]
[null,null,null,null,null,null,null,2,3,6]
class StackOfPlates {
public:
vector> vec_stk;
int stk_cap = 0;
StackOfPlates(int cap) {
stk_cap = cap;
// stack stk;
// vec_stk.push_back(stk);
}
void push(int val) {
if(stk_cap == 0) return;
if(vec_stk.empty() || vec_stk.back().size() == stk_cap){
stack stk;
stk.push(val);
vec_stk.push_back(stk);
}
else{
vec_stk.back().push(val);
}
}
int pop() {
if(stk_cap == 0) return -1;
if(vec_stk.empty()){
return -1;
}
int val = vec_stk.back().top();
vec_stk.back().pop();
if(vec_stk.back().size() == 0) vec_stk.pop_back();
return val;
}
int popAt(int index) {
if(stk_cap == 0) return -1;
int n = vec_stk.size();
if(index < n && vec_stk[index].size() > 0){
int val = vec_stk[index].top();
vec_stk[index].pop();
if(vec_stk[index].empty()){
vec_stk.erase(vec_stk.begin() + index);
}
// reshape_vec_stk();
return val;
}
return -1;
}
void reshape_vec_stk(){
int n = vec_stk.size();
int cur_stk_ind = 0;
for(int i = 0; i < n; ++i){
if(cur_stk_ind == i && vec_stk[i].size() == stk_cap){
continue;
}
if(vec_stk[i].size() == 0){
continue;
}
else{
stack tmp;
while(vec_stk[i].size() > 0){
tmp.push(vec_stk[i].top());
vec_stk[i].pop();
}
while(tmp.size() > 0 && vec_stk[cur_stk_ind].size() < stk_cap){
vec_stk[cur_stk_ind].push(tmp.top());
tmp.pop();
}
while(tmp.size() > 0){
vec_stk[i].push(tmp.top());
tmp.pop();
}
if(vec_stk[cur_stk_ind].size() == stk_cap){
++cur_stk_ind;
}
}
}
}
};
/**
* Your StackOfPlates object will be instantiated and called as such:
* StackOfPlates* obj = new StackOfPlates(cap);
* obj->push(val);
* int param_2 = obj->pop();
* int param_3 = obj->popAt(index);
*/
-
实现一个魔法字典(题目编号676:link,2021.8.22)
- 利用字典,记录dictionary中每个单词每一位替换成空格的结果出现的次数,得到“替换字典”,同时用字典记录完整的dictionary,得到“原始字典”。
- 搜索时,将目标词每一位替换成空格,判断“替换字典”中是否出现至少2次(表示dictionary中至少有2个单词可以只替换一位得到目标词,如果只出现1次,则需要判断目标词在原始字典中是否出现)
class MagicDictionary {
unordered_map ump;
unordered_map dict;
public:
/** Initialize your data structure here. */
MagicDictionary() {
}
void buildDict(vector dictionary) {
char tmp;
for(auto& word: dictionary){
dict[word] = 1;
for(int i = 0; i < word.size(); ++i){
tmp = word[i];
word[i] = ' ';
++ump[word];
word[i] = tmp;
}
}
}
bool search(string searchWord) {
char tmp;
string oriWord = searchWord;
for(int i = 0; i < searchWord.size(); ++i){
tmp = searchWord[i];
searchWord[i] = ' ';
if(ump[searchWord] > 1 || (ump[searchWord] == 1 && dict[oriWord] == 0)){
return true;
}
searchWord[i] = tmp;
}
return false;
}
};
/**
* Your MagicDictionary object will be instantiated and called as such:
* MagicDictionary* obj = new MagicDictionary();
* obj->buildDict(dictionary);
* bool param_2 = obj->search(searchWord);
*/
- 小里程碑,题目通过500道。
-
从英文中重建数字(题目编号:423:link,2021.8.27)
- 乍一看题目有点难,仔细一想,每个数字的英文字母是有一些特殊字符的,比如five的v,在其他数字中是没有的,因此可以一次一个从26个字母出现次数中提取出独一无二的特殊字符。
class Solution {
public:
string originalDigits(string s) {
// zero one two three four five six seven eight nine
vector> pairs{
{"zero", "z", "0"},
{"eight", "g", "8"},
{"four", "u", "4"},
{"five", "f", "5"},
{"three", "h", "3"},
{"two", "t", "2"},
{"six", "x", "6"},
{"nine", "i", "9"},
{"seven", "v", "7"},
{"one", "o", "1"}
};
vector cnt(26, 0);
vector num_cnt(10, 0);
for(char c: s){
++cnt[c - 'a'];
}
for(auto item: pairs){
char sp_char = item[1][0];
int real_num = item[2][0] - '0';
num_cnt[real_num] = cnt[sp_char - 'a'];
for(char c: item[0]){
cnt[c - 'a'] -= num_cnt[real_num];
}
}
string ans;
for(int i = 0; i <= 9; ++i){
if(num_cnt[i] > 0) ans += string(num_cnt[i], '0' + i);
}
return ans;
}
};
-
零钱兑换II(题目编号518:link,2021.8.27)
class Solution {
public:
int change(int amount, vector& coins) {
vector dp(amount + 1);
dp[0] = 1;
for(int c: coins){
for(int i = c; i <= amount; ++i){
dp[i] += dp[i - c];
}
}
return dp[amount];
}
};
-
剑指Offer II 085. 生成匹配的括号(链接:link,2021.8.27)
class Solution {
public:
vector generateParenthesis(int n) {
vector ans;
dfs(ans, "", 0, n);
return ans;
}
void dfs(vector& ans, string cur, int left_cnt, const int n){
if(cur.size() == n * 2){
ans.push_back(cur);
return;
}
if(left_cnt < n){
cur += "(";
dfs(ans, cur, left_cnt + 1, n);
cur.pop_back();
}
if(left_cnt * 2 > cur.size()){
cur += ")";
dfs(ans, cur, left_cnt, n);
}
}
};
-
吃苹果的最大数目(题目编号1705:link,2021.8.27)
- 核心思路是用优先队列或者map存储日期与这天会过期的苹果数量映射,每天优先吃快过期的。
class Solution {
public:
int eatenApples(vector& apples, vector& days) {
map day2cnt;
int res = 0;
int n = apples.size();
for(int i = 0; i < n || !day2cnt.empty(); ++i){
day2cnt.erase(i);
if(i < n && apples[i] > 0){
day2cnt[i + days[i]] += apples[i];
}
if(!day2cnt.empty()){
auto iter = day2cnt.begin();
--iter->second;
++res;
if(iter->second == 0){
day2cnt.erase(iter);
}
}
}
return res;
}
};
-
剑指Offer II 046. 二叉树的右侧视图(链接:link,2021.8.27)
- 记录当前达到的最大深度,如果当前深度突破了,那么就可以在右视图中看到,记录下来。注意是右视图,因此深度优先遍历从右孩子先处理。
/**
* 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 rightSideView(TreeNode* root) {
vector ans;
int maxDepth = 0;
dfs(ans, root, maxDepth, 0);
return ans;
}
void dfs(vector& ans, TreeNode* root, int& maxDepth, int curDepth){
if(!root) return;
++curDepth;
if(curDepth > maxDepth){
ans.push_back(root->val);
maxDepth = curDepth;
}
dfs(ans, root->right, maxDepth, curDepth);
dfs(ans, root->left, maxDepth, curDepth);
}
};
-
网络延迟时间(题目编号743:link,2021.8.27)
- 最开始自己写的算法,可以通过一半的样例。注意其中的visited,避免重复走。最后超时了才想起来要用Dijstra算法。
class Solution {
public:
int networkDelayTime(vector>& times, int n, int k) {
map> node2adj;
map, int> conn_cost;
vector min_cost(n, INT_MAX);
for(auto v: times){
node2adj[v[0] - 1].push_back(v[1] - 1);
conn_cost[vector{v[0] - 1, v[1] - 1}] = v[2];
}
vector adj = node2adj[k - 1];
vector visited(n);
min_cost[k - 1] = 0;
if(adj.size() > 0){
visited[k - 1] = true;
for(int tar: adj){
update(min_cost, node2adj, conn_cost, k - 1, tar, visited);
}
}
int ans = *max_element(min_cost.begin(), min_cost.end());
return ans == INT_MAX ? -1: ans;
}
void update(vector& min_cost, map>& node2adj, map, int>& conn_cost, int src, int tar, vector visited){
if(visited[tar]) return;
visited[tar] = true;
min_cost[tar] = min(min_cost[tar], min_cost[src] + conn_cost[vector{src, tar}]);
for(int t: node2adj[tar]){
if(t != src && !visited[t]){
update(min_cost, node2adj, conn_cost, tar, t, visited);
}
}
}
};
- Dijstra算法:单源最短路径算法,将所有节点分成两类:已确定从起点到当前点的最短路长度的节点,以及未确定从起点到当前点的最短路长度的节点(下面简称「未确定节点」和「已确定节点」)。每次从「未确定节点」中取一个与起点距离最短的点,将它归类为「已确定节点」,并用它「更新」从起点到其他所有「未确定节点」的距离。直到所有点都被归类为「已确定节点」。
class Solution {
public:
int networkDelayTime(vector>& times, int n, int k) {
const int inf = INT_MAX / 2;
vector> g(n, vector(n, inf));
for(auto& t: times){
int x = t[0] - 1, y = t[1] - 1;
g[x][y] = t[2];
}
vector dist(n, inf);
dist[k - 1] = 0;
vector used(n);
for(int i = 0; i < n; ++i){
int x = -1;
for(int y = 0; y < n; ++y){
if(!used[y] && (x == -1 || dist[y] < dist[x])){
x = y;
}
}
used[x] = true;
for(int y = 0; y < n; ++y){
dist[y] = min(dist[y], dist[x] + g[x][y]);
}
}
int ans = *max_element(dist.begin(), dist.end());
return ans == inf ? -1: ans;
}
};
-
插入、删除和随机访问都是 O(1) 的容器(题目编号:剑指offerII 面试题30,link)
- 数据用数组存储才能随机访问O(1)、插入O(1)、删除O(1)。需要查询数据是否存在也是O(1)的话需要用哈希表。
class RandomizedSet {
private:
unordered_map ump;
vector nums;
public:
/** Initialize your data structure here. */
RandomizedSet() {
}
/** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
bool insert(int val) {
if(ump.count(val)){
return false;
}
nums.push_back(val);
ump[val] = nums.size() - 1;
return true;
}
/** Removes a value from the set. Returns true if the set contained the specified element. */
bool remove(int val) {
if(ump.count(val)){
int remove_pose = ump[val];
nums[remove_pose] = nums.back();
ump[nums.back()] = remove_pose;
nums.pop_back();
ump.erase(val);
return true;
}
return false;
}
/** Get a random element from the set. */
int getRandom() {
return nums[rand() % nums.size()];
}
};
/**
* Your RandomizedSet object will be instantiated and called as such:
* RandomizedSet* obj = new RandomizedSet();
* bool param_1 = obj->insert(val);
* bool param_2 = obj->remove(val);
* int param_3 = obj->getRandom();
*/
-
获取好友已观看的视频(题目编号1311:link)
- 第k层好友关系用广度优先遍历得到
- 注意排序时频次相等时的排序规则
using PSI = pair;
class Solution {
public:
vector watchedVideosByFriends(vector>& watchedVideos, vector>& friends, int id, int level) {
int n = watchedVideos.size();
vector ans;
if(id < 0 || id >= n || level < 1 || level > n) return ans;
unordered_map visited;
queue kfriends;
kfriends.push(id);
visited[id] = true;
int round = 0;
while(round++ < level){
int sz = kfriends.size();
while(sz--){
int cur_id = kfriends.front();
kfriends.pop();
for(auto& k: friends[cur_id]){
if(visited[k]){
continue;
}
visited[k] = true;
kfriends.push(k);
}
}
}
unordered_map count;
while(kfriends.size()){
int cur_id = kfriends.front();
kfriends.pop();
for(auto& k: watchedVideos[cur_id]){
++count[k];
}
}
vector videos(count.begin(), count.end());
sort(videos.begin(), videos.end(), func);
for(auto& item:videos){
ans.push_back(item.first);
}
return ans;
}
static bool func(PSI a, PSI b){
return a.second < b.second || (a.second == b.second && a.first < b.first);
}
};
-
子字符串突变后可能得到的最大整数(题目编号1946:link)
- 贪心法,从前往后找第一个会变大的数开始,然后往后找第一个会变小的数停止。
class Solution {
public:
string maximumNumber(string num, vector& change) {
int n = num.size();
for(int i = 0; i < n; ++i){
if(change[num[i] - '0'] > num[i] - '0'){
while(i < n && change[num[i] - '0'] >= num[i] - '0'){
num[i] = '0' + change[num[i] - '0'];
++i;
}
break;
}
}
return num;
}
};
-
解码异或后的排列(题目编号1734:link,2021.9.26)
- 异或问题就要利用异或的2个性质:a ^ 0 = a,a ^ a=0,a表示任意数字。
- encoded数组构成为perm[0] ^ perm[1],perm[1] ^ perm[2], perm[2] ^ perm[3]…分析可以发现,由于n是奇数,那么encoded数组的长度是偶数,那么如果间隔取encoded数组的值相与,取0、2、4位置处,那么就只剩下perm[n-1]未参与“与”运算,得到的值记为xor_except_last,而1~n所有数字的与记为xor_all,那么两个xor值再进行与运算,就只有perm[n-1]只参与了1次与运算,最后的结果就是perm[n-1]。有了perm[n-1],就可以利用encoded数组倒序推断出perm[n-2]到perm[0]。
class Solution {
public:
vector decode(vector& encoded) {
int xor_all = 0, n = encoded.size() + 1;
for(int i = 1; i <= n; ++i){
xor_all ^= i;
}
int xor_except_last = 0;
for(int i = 0; i < n - 1; i += 2){
xor_except_last ^= encoded[i];
}
vector perm(n);
perm[n - 1] = xor_except_last ^ xor_all;
for(int i = n - 2; i >= 0; --i){
perm[i] = encoded[i] ^ perm[i + 1];
}
return perm;
}
};
-
二叉搜索树与双向链表(题目编号:剑指offer36,link,2021.9.26)
- 树的题目很多采用递归法求解,递归法关键是找到递归终止条件,以及终止返回上一层次后如何处理。本题选择使用vector记录当前已经转换完成的双向链表的头结点和尾节点。题目要求是双向循环链表,因此最后还要将头尾节点连起来。
- 该方法速度很快,但内存占用比较多。
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node() {}
Node(int _val) {
val = _val;
left = NULL;
right = NULL;
}
Node(int _val, Node* _left, Node* _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
class Solution {
public:
Node* treeToDoublyList(Node* root) {
if(!root) return root;
vector ans = func(root);
ans[0]->left = ans[1];
ans[1]->right = ans[0];
return ans[0];
}
vector func(Node* root){
vector vec{root, root};
if(!root) return vec;
if(root->left){
vector tmp_vec = func(root->left);
tmp_vec[1]->right = root;
root->left = tmp_vec[1];
vec[0] = tmp_vec[0];
}
if(root->right){
vector tmp_vec = func(root->right);
root->right = tmp_vec[0];
tmp_vec[0]->left = root;
vec[1] = tmp_vec[1];
}
return vec;
}
};
- 参考题解区的答案,获得内存上更优的方法。代码更简洁。
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node() {}
Node(int _val) {
val = _val;
left = NULL;
right = NULL;
}
Node(int _val, Node* _left, Node* _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
class Solution {
private:
Node *pre = nullptr, *head = nullptr;
public:
Node* treeToDoublyList(Node* root) {
if(!root) return root;
dfs(root);
pre->right = head;
head->left = pre;
return head;
}
void dfs(Node* root){
if(!root) return;
dfs(root->left);
if(!pre){
head = root;
}
else{
pre->right = root;
}
root->left = pre;
pre = root;
dfs(root->right);
}
};
-
最长连续序列(题目编号:剑指Offer II 119,link,2021.9.26)
- 利用map/set的快速搜索特性,从中取出一个数字,然后往前往后判断数字是否存在,计算长度,并从map/set中删除对应的连续数字。注意,要记得删除当前数字,否则会死循环。
class Solution {
public:
int longestConsecutive(vector& nums) {
unordered_set st;
for(auto& n: nums){
st.insert(n);
}
int ans = 0;
while(st.size()){
int n = *st.begin();
int left = n - 1;
while(st.count(left)){
st.erase(left);
--left;
}
int right = n + 1;
while(st.count(right)){
st.erase(right);
++right;
}
ans = max(ans, right - left - 1);
st.erase(n);
}
return ans;
}
};
-
扣分后的最大得分(link,2021.9.29)
- 这题肯定要用动态规划的方法,用dp[i][j]表示最后一次选第i行第j列的最大得分,但是求解dp[i][j]需要遍历dp[i-1][j’],因此时间复杂度是mn^2。dp[i][j] = max_j’{dp[i-1][j’]+|j’-j|}+points[i][j]。
- 上述动态规划算法可以优化,对j’j,正向和逆向遍历求解dp[i][j],时间复杂度O(mn)。
- 思路不太好理解,以后再思考一下。
class Solution {
public:
long long maxPoints(vector>& points) {
int m = points.size();
if(m == 0) return 0;
int n = points[0].size();
vector f(m);
for(int i = 0; i < m; ++i){
vector g(n);
long long best = LLONG_MIN;
for(int j = 0; j < n; ++j){
best = max(best, f[j] + j);
g[j] = max(g[j], best + points[i][j] - j);
}
best = LLONG_MIN;
for(int j = n - 1; j >= 0; --j){
best = max(best, f[j] - j);
g[j] = max(g[j], best + points[i][j] + j);
}
f = move(g);
}
return *max_element(f.begin(), f.end());
}
};
-
火柴拼正方形(题目编号473:link,2021.9.29)
- 回溯法,用4维数据维护每条边的长度。关键是剪枝的技巧,如果当前边的长度超过1/4总长,跳过,如果当前边和上一条边的长度一样,也跳过,上一条边这个长度搜不到,当前边也搜不到。
class Solution {
public:
bool makesquare(vector& matchsticks) {
int sum = 0;
for(auto& num: matchsticks){
sum += num;
}
if(sum == 0 || sum % 4 != 0) return false; //剪枝
vector adds(4, 0);
sort(matchsticks.begin(), matchsticks.end(), greater());
return dfs(0, adds, matchsticks, sum);
}
bool dfs(int beg, vector& adds, vector& matchsticks, int sum){
if(*max_element(adds.begin(), adds.end()) > sum / 4) return false;
if(beg == matchsticks.size()){
if(adds[0] == adds[1] && adds[1] == adds[2] && adds[2] == adds[3]){
return true;
}
return false;
}
for(int i = 0; i < 4; ++i){
if(adds[i] + matchsticks[beg] > sum / 4 || (i > 0 && adds[i] == adds[i - 1])) continue; //关键的剪枝
adds[i] += matchsticks[beg];
if(dfs(beg+1, adds, matchsticks, sum)) return true;
adds[i] -= matchsticks[beg];
}
return false;
}
};
-
设计地铁系统(题目编号1396:link,2021.9.29)
class UndergroundSystem {
private:
map> mp_in;
map, pair> mp_time;
public:
UndergroundSystem() {
}
void checkIn(int id, string stationName, int t) {
mp_in[id] = pair(stationName, t);
}
void checkOut(int id, string stationName, int t) {
string startStationName = mp_in[id].first;
int startTime = mp_in[id].second;
double meanTime = mp_time[pair(startStationName, stationName)].first;
int count = mp_time[pair(startStationName, stationName)].second;
pair new_result = pair(count * 1.0 / (count + 1) * meanTime + (t - startTime) * 1.0 / (count + 1), count + 1);
mp_time[pair(startStationName, stationName)] = new_result;
}
double getAverageTime(string startStation, string endStation) {
return mp_time[pair(startStation, endStation)].first;
}
};
/**
* Your UndergroundSystem object will be instantiated and called as such:
* UndergroundSystem* obj = new UndergroundSystem();
* obj->checkIn(id,stationName,t);
* obj->checkOut(id,stationName,t);
* double param_3 = obj->getAverageTime(startStation,endStation);
*/
-
爱吃香蕉的珂珂(题目编号875:link,2021.10.6)
- 最容易想到的方法是从小到大遍历K,看哪个速度可以吃完。
- 二分搜索法:若最终答案为T,那么对于K=T,是都可以吃完的。单调函数可以采用二分搜索的方法,加速搜索。
class Solution {
public:
int minEatingSpeed(vector& piles, int h) {
int lo = 1, hi = pow(10, 9);
while(lo < hi){
int mid = lo + (hi - lo) / 2;
if(check(piles, h, mid)){
hi = mid;
}
else{
lo = mid + 1;
}
}
return hi;
}
bool check(vector& piles, int h, int K){
int time = 0;
for(auto& p: piles){
time += (p - 1) / K + 1;
}
return time <= h;
}
};
-
单词长度的最大乘积(题目编号:剑指Offer II 005,link,2021.10.6)
- 用一个26位int数字hash值表示字符串出现过哪些字符,两个hash值按位与,如果结果为0,则说明没有重复字符。
class Solution {
public:
int maxProduct(vector& words) {
int n = words.size();
if(n <= 1) return 0;
vector hash(n);
for(int i = 0; i < n; ++i){
for(auto& c: words[i]){
hash[i] |= 1 << (c - 'a');
}
}
int ans = 0;
for(int i = 0; i < n - 1; ++i){
for(int j = i + 1; j < n; ++j){
int tmp = words[i].size() * words[j].size();
if(tmp > ans && ((hash[i] & hash[j]) == 0)){
ans = tmp;
}
}
}
return ans;
}
};
-
将字符串拆成递减的连续值(题目编号:1849,link,2021.10.6)
- 这道题目耗费了很长时间,主要在解决各种特殊用例和整形数越界的问题。
- 思路是先确定递减数列的第一个数num,然后后续依次搜索n-1,n-2,等等。
- 特殊情况包括:
降序后某一个数变成0,后续只需全0即可,不需再寻找-1。
第一个数就是0,这种情况是不符合题目要求的。
class Solution {
public:
bool splitString(string s) {
int n = s.size();
for(int digit = 1; digit <= n - 1; ++digit){
unsigned long long num = 0;
for(int i = 0; i < digit; ++i){
num = num * 10 + (s[i] - '0');
}
if(num == 0) continue; //解决第一个数字就是0的问题
int ind = digit;
bool ans_flag = true, complete_flag = false;
while(ind < n){
unsigned long long t_num = 0;
complete_flag = false;
while(ind < n){
if(num == 0){
while(ind < n && s[ind] == '0'){
++ind;
}
//解决降序数变为0的特殊情况,之后只要全是0就行。
if(ind == n){
return true;
}
ans_flag = false;
break;
}
t_num = t_num * 10 + (s[ind] - '0');
++ind;
if(t_num == num - 1){
complete_flag = true;
break;
}
else if(t_num > num - 1){
ans_flag = false;
break;
}
}
if(!ans_flag){
break;
}
num = t_num;
}
if(ans_flag && complete_flag){
return true;
}
}
return false;
}
};
-
删除最短的子数组使剩余数组有序(题目编号1574:link,2021.10.23)
- 关键是要理解题目,子数组是连续的,子序列一般指不连续的。
- 先找到左边递增序列和右边的递增数列,如果连起来是递增的,就把中间删掉,如果不严格递增,就要找到哪里是最合适的连接点。
- 遍历左边的递增序列,在右边的递增序列里找到对应的连接点,可以采用二分搜索,
class Solution {
public:
int findLengthOfShortestSubarray(vector& arr) {
int left = 1, n = arr.size(), right = n - 1;
while(left < n && arr[left - 1] <= arr[left]) ++left;
while(right >= 1 && arr[right - 1] <= arr[right]) --right;
if(left > right) return 0;
int ans = right;
for(int i = 0; i < left; ++i){
int e = arr[i];
int pos = lower_bound(arr.begin() + right, arr.end(), e) - arr.begin();
ans = min(ans, pos - i - 1);
}
return ans;
}
};
-
所有子集(题目编号:剑指Offer II 079,link,2021.10.23)
- 这个就是简单的遍历搜索,分为用和不用当前数字,然后递归搜索下一位。
class Solution {
public:
vector> subsets(vector& nums) {
vector> ans;
int n = nums.size();
traverse(ans, nums, n, 0, vector());
return ans;
}
void traverse(vector>& ans, vector& nums, int& n, int cur_ind, vector cur_subset){
if(cur_ind == n){
ans.push_back(cur_subset);
return;
}
traverse(ans, nums, n, cur_ind + 1, cur_subset);
cur_subset.push_back(nums[cur_ind]);
traverse(ans, nums, n, cur_ind + 1, cur_subset);
}
};
-
到达目的地的方案数(题目编号:1976,link,2021.10.23)
- 题目的解决分为几步,首先是寻找起点到终点的最短距离,使用dijstra算法,然后构造有向图g,如果满足dist[0][j]-dist[0][i]=dist[i][j],那么存在从i到j的边(可证明是无环的),接着采用动态规划,求解最终的方案数,用数组f表示,f[n-1]=1,倒序前推,对于i,遍历g中有边连接的下一个节点j,对方案数求和,最终的f[0]即为答案。
- 这道题综合了最短路径和动态规划,适合面试。
- 下面解法涉及到了function模板和lambda函数,都是知识点。参考此博客
class Solution {
private:
int mod = pow(10, 9) + 7;
public:
int countPaths(int n, vector>& roads) {
vector> dist(n, vector(n, LLONG_MAX / 2));
for(int i = 0; i < n; ++i) dist[i][i] = 0;
for(auto&& info: roads){
int a = info[0], b = info[1], d = info[2];
dist[a][b] = dist[b][a] = d;
}
//Dijstra
vector used(n);
for(int _ = 0; _ < n; ++_){
int u = -1;
for(int i = 0; i < n; ++i){
if(!used[i] && (u == -1 || dist[0][i] < dist[0][u])){
u = i;
}
}
used[u] = true;
for(int i = 0; i < n; ++i){
dist[0][i] = min(dist[0][i], dist[0][u] + dist[u][i]);
}
}
//构造图G
vector> g(n);
for(auto&& info: roads){
int a = info[0], b = info[1], d = info[2];
if(dist[0][b] - dist[0][a] == d){
g[a].push_back(b);
}
else if(dist[0][a] - dist[0][b] == d){
g[b].push_back(a);
}
}
//最终结果
vector f(n, -1);
function dfs = [&](int u){
if(u == n - 1) return 1;
if(f[u] != -1) return f[u];
f[u] = 0;
for(auto& v: g[u]){
f[u] += dfs(v);
if(f[u] > mod){
f[u] -= mod;
}
}
return f[u];
};
return dfs(0);
}
};
-