最近开始用c++写算法,浅浅的记录一下打卡题。
之后写的题会持续在里面更新!
栈与队列(简单)4.25
剑指 Offer 09. 用两个栈实现队列
思路:题目需要利用两个栈实现队列,队列是一端进一端出,我们可以将两个栈拼接起来,一个栈作为队尾进元素,将这个栈的元素弹出到另一个栈,另一个栈作为队头输出,就可以实现了。
代码:
class CQueue {
public:
stack <int> headStack;
stack <int> frontStack;
CQueue() {
}
void appendTail(int value) {
headStack.push(value);
}
int deleteHead() {
if (headStack.empty()) {
return -1;
} else {
while (!headStack.empty()) {
frontStack.push(headStack.top());
headStack.pop();
}
int val = frontStack.top();
frontStack.pop();
while (!frontStack.empty()) {
headStack.push(frontStack.top());
frontStack.pop();
}
return val;
}
}
};
剑指 Offer 30. 包含min函数的栈
思路:首先设置两个栈,第一个栈用来记录输入的元素,第二个栈用于保存数据最小值,这个题的关键在于如何对最小栈进行压入,当栈空时,直接压入第一个元素,并默认此时这个元素为最小值,在压入后续元素时,进行比对,如果小了,就压入栈中,作为新的最小值,如果不是,那么就继续压入一个当前最小值元素,这样做对目的是,在后续两个栈弹出元素时,可以保证最小栈栈顶永远是最小的。
class MinStack {
public:
/** initialize your data structure here. */
stack <int> myStack;
stack <int> minStack;
MinStack() {
}
void push(int x) {
myStack.push(x);
if (minStack.empty()) {
minStack.push(x);
} else {
if (x < minStack.top()) {
minStack.push(x);
} else {
minStack.push(minStack.top());
}
}
}
void pop() {
myStack.pop();
minStack.pop();
}
int top() {
return (int)myStack.top();
}
int min() {
return minStack.top();
}
};
链表(简单)4.26
剑指 Offer 06. 从尾到头打印链表
很简单可以用个栈记录输入元素,然后给到容器里就行,这里直接调用的数组的函数,让元素一直插入在头部位置。
这里直接调库,循环一遍就行了。
class Solution {
public:
vector<int> reversePrint(ListNode* head) {
ListNode *temp = head;
vector <int> ans;
while (temp) {
ans.insert(ans.begin(), temp->val);
temp = temp->next;
}
return ans;
}
};
剑指 Offer 24. 反转链表
利用三指针原地反转
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *temp;
ListNode *nextTemp;
ListNode *p;
temp = head;
p = NULL;
nextTemp = NULL;
while (temp) {
nextTemp = temp->next;
temp->next = p;
p = temp;
temp = nextTemp;
}
return p;
}
};
剑指 Offer 35. 复杂链表的复制
思路:这题的难点就在于增加指针是随机的,要做到深拷贝就需要指向新生成的随机结点,这里我的做法是在原来的结点后新生成一个结点,第一遍仅初始化next指针和值,第二遍遍历时,因为新的结点已经生成,根据旧结点的指向给新结点的random赋值,最后将链表新结点取出即可。
class Solution {
public:
Node* copyRandomList(Node* head) {
if (head == NULL) {
return head;
}
Node* temp = head;
while(temp) {
Node *newNode = new Node(temp->val);
newNode->next = temp->next;
temp->next = newNode;
temp = newNode->next;
}
temp = head;
while(temp) {
if (temp->random != NULL) {
temp->next->random = temp->random->next;
}
temp = temp->next->next;
}
Node *ans = head->next;
temp = head;
Node *copyNode = head->next;
while(temp) {
temp->next = temp->next->next;
temp = temp->next;
if (copyNode->next) {
copyNode->next = copyNode->next->next;
copyNode = copyNode->next;
}
}
return ans;
}
};
字符串(简单)4.27
剑指 Offer 05. 替换空格
思路:找到空格 然后替换就行 很简单
class Solution {
public:
string replaceSpace(string s) {
for (int i = 0; i < s.length(); i++) {
if (s[i] == ' ') {
s.replace(i, 1, "%20");
}
}
return s;
}
};
剑指 Offer 58 - II. 左旋转字符串
左边的拼到右边 把左边删了
class Solution {
public:
string reverseLeftWords(string s, int n) {
s += s.substr(0, n);
s.erase(0,n);
return s;
}
};
还有一种方法开辟两个新空间,return两个拼接,但是很占内存。
class Solution {
public:
string reverseLeftWords(string s, int n) {
string ch1 = s.substr(0, n);
string ch2 = s.substr(n, s.length());
return ch2 + ch1;
}
};
查找算法(简单)4.28
剑指 Offer 03. 数组中重复的数字
很容易想到两种思路,第一种就是先排序,然后找有没有相邻相同的,这种方法不开新空间,但时间复杂度较高.
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++) {
if (nums[i] == nums[i + 1]) {
return nums[i];
}
}
return 0;
}
};
另外一种就是利用哈希表统计元素个数,时间复杂度低,但是需要开辟空间,
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
unordered_map<int, int> map;
for(int num : nums){
map[num]++;
if(map[num] >= 2) {
return num;
}
return nums[nums.size() - 1];
}
};
剑指 Offer 53 - I. 在排序数组中查找数字 I
可以用哈希也可以用二分查找,感觉这题二分查找比较合适
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int count = 0;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] >= target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
while(left < nums.size() && nums[left++] == target) {
count++;
}
return count;
}
};
剑指 Offer 53 - II. 0~n-1中缺失的数字
第一思路是遍历:
class Solution {
public:
int missingNumber(vector<int>& nums) {
int icount = 0;
for (int num : nums) {
if (num != icount) {
break;
}
icount++;
}
return icount;
}
};
不过对于有序数组最优解应该还是二分查找。
通过二分判断前面少了数还是后面少了数
不过这个题的测试样例有点问题,我的评价是不如遍历
class Solution {
public:
int missingNumber(vector<int>& nums) {
int left = 0;
int right = nums.size() - 1;
while (left < right) {
int mid = (left + right) / 2;
if (mid == nums[mid]) {
left = mid + 1;
} else {
right = mid;
}
}
return left == nums.size() - 1 && nums[left] == left ? left + 1 : left;
}
};
查找算法(中等)4.28
剑指 Offer 04. 二维数组中的查找
思路;这个题可以把二维数组当作一个矩形,然后找个数去查找,然后就可以排除区域去寻找,但这样会有重复的地方,这里再看看题的规律随机选取一个数,他的左边一定比他小,下面一定比他大,这里就想到了二叉排序树。可以从右上角开始查找,如果比他大,就找他的下一行,如果比他小,就找他的左一列,这样很快就能找到了。
lass Solution {
public:
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
if(matrix.size() == 0 || matrix[0].size() == 0) {
return false;
}
int column = matrix[0].size() - 1;
int row = 0;
int ans = 0;
while (row < matrix.size() && column >= 0) {
if (target == matrix[row][column]) {
ans = 1;
break;
}
if (target < matrix[row][column]) {
column--;
} else if (target > matrix[row][column]) {
row++;
}
}
if (ans == 1) {
return true;
} else {
return false;
}
}
};
剑指 Offer 11. 旋转数组的最小数字
这个题很简单,排序遍历二分都可以
class Solution {
public:
int minArray(vector<int>& numbers) {
sort(numbers.begin(), numbers.end());
return numbers[0];
}
};
class Solution {
public:
int minArray(vector<int>& numbers) {
for (int i = 1; i < numbers.size(); i++) {
if (numbers[i] < numbers[i - 1]) {
return numbers[i];
}
}
return numbers[0];
}
};
cclass Solution {
public:
int minArray(vector<int>& numbers) {
int i = 0, j = numbers.size() - 1;
while (i < j) {
int m = (i + j) / 2;
if (numbers[m] > numbers[j]) {
i = m + 1;
} else if (numbers[m] < numbers[j]) {
j = m;
}
else {
j--;
}
}
return numbers[i];
}
};
剑指 Offer 50. 第一个只出现一次的字符
利用hash表映射频率
class Solution {
public:
char firstUniqChar(string s) {
unordered_map<int, int> map;
for (char ch : s) {
map[ch]++;
}
for (int i = 0; i < s.size(); i++) {
if (map[s[i]] == 1) {
return s[i];
}
}
return ' ';
}
};
搜索与回溯算法(简单)4.29
剑指 Offer 32 - I. 从上到下打印二叉树
思路:层次遍历,可以利用队列去做,先将根部压入对列,之后压入左和右,然后弹出队首元素到数组,循环条件为对列不为空。
class Solution {
public:
queue<TreeNode *> testQueue;
vector<int> ansVector;
vector<int> levelOrder(TreeNode* root) {
if (root == NULL) {
return ansVector;
} else {
testQueue.push(root);
while (testQueue.size()) {
TreeNode* temp = testQueue.front();
ansVector.push_back(temp->val);
if (temp->left) {
testQueue.push(temp->left);
}
if (temp->right) {
testQueue.push(temp->right);
}
testQueue.pop();
}
}
return ansVector;
}
};
剑指 Offer 32 - II. 从上到下打印二叉树 II
思路:也是层次遍历只不过加了一个行和行剩余元素判断,这里注意给二维数组添加时只能添加vector。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode *> testQueue;
vector<vector<int>> ansVector;
int number = 1;
int row = 0;
if (!root) {
return ansVector;
} else {
testQueue.push(root);
vector<int> tempVector;
while (testQueue.size()) {
TreeNode* temp = testQueue.front();
tempVector.push_back(temp->val);
if (temp->left) {
testQueue.push(temp->left);
}
if (temp->right) {
testQueue.push(temp->right);
}
testQueue.pop();
number--;
if (number == 0) {
number = testQueue.size();
row++;
ansVector.push_back(tempVector);
tempVector.clear();
}
}
}
return ansVector;
}
};
剑指 Offer 32 - III. 从上到下打印二叉树 III
加一个数组反转
/**
* 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:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode *> testQueue;
vector<vector<int>> ansVector;
int number = 1;
int row = 0;
if (!root) {
return ansVector;
} else {
testQueue.push(root);
vector<int> tempVector;
while (testQueue.size()) {
TreeNode* temp = testQueue.front();
tempVector.push_back(temp->val);
if (temp->left) {
testQueue.push(temp->left);
}
if (temp->right) {
testQueue.push(temp->right);
}
testQueue.pop();
number--;
if (number == 0) {
number = testQueue.size();
row++;
if (row % 2 == 0) {
reverse(tempVector.begin(), tempVector.end());
}
ansVector.push_back(tempVector);
tempVector.clear();
}
}
}
return ansVector;
}
};
搜索与回溯算法(简单)5.1
剑指 Offer 26. 树的子结构
一开始看见这个题的时候有点懵,看了一下评论建议全文背诵默写,理了一遍思路写完了。
这里使用的是递归的思想,首先要针对传入的指针地址是否为空作特殊处理,无论A空还是B空我们都得返回false。
接下来我们现在A数中找到和B根结点值相同的结点,然后开始对比他的左子树和右子树是否相等,结束条件为A空,B空,以及值不相等,对应了false,true,false。
这里提及一个点题上的val是int值,如果是double的时候,因为精度存在差异,所以我们不能直接用==判断,这个时候我们应该写一个Equal去判断,如果精度在0.000001之内,我们就默认他俩相等。下面是代码:
class Solution {
public:
bool isSubStructure(TreeNode* A, TreeNode* B) {
if (A == NULL || B == NULL) {
return false;
}
return searchSubTreeNode(A, B);
}
bool compareTreeNode(TreeNode* A, TreeNode* B) {
if (B == NULL) {
return true;
}
if (A == NULL) {
return false;
}
return A->val == B->val && compareTreeNode(A->left, B->left) && compareTreeNode(A->right, B->right);
}
bool searchSubTreeNode(TreeNode* A, TreeNode* B) {
if (A == NULL) {
return false;
}
if (A->val == B->val && compareTreeNode(A, B)) {
return true;
}
return searchSubTreeNode(A->left, B) || searchSubTreeNode(A->right, B);
}
};
这个是第一次的代码,看了评论区大神短短六行,尝试优化了一下:
bool searchSubTreeNode(TreeNode* A, TreeNode* B) {
if (A == NULL) {
return false;
}
if (A->val == B->val && compareTreeNode(A, B)) {
return true;
}// 可以发现这里值对比是多余的,所以可以整合到最后
return searchSubTreeNode(A->left, B) || searchSubTreeNode(A->right, B);
// 这个函数也就是遍历寻找结点,我们可以直接在主函数中遍历
}
于是可以写成下面的高质量代码:
class Solution {
public:
bool isSubStructure(TreeNode* A, TreeNode* B) {
if (A == NULL || B == NULL) {
return false;
}
return compareTreeNode(A, B) || isSubStructure(A->left, B) || isSubStructure(A->right, B);
}
bool compareTreeNode(TreeNode* A, TreeNode* B) {
if (B == NULL) {
return true;
}
if (A == NULL) {
return false;
}
return A->val == B->val && compareTreeNode(A->left, B->left) && compareTreeNode(A->right, B->right);
}
};
这样的话看起来会整洁许多。
写了一天树感觉自己很拉。
剑指 Offer 27. 二叉树的镜像
递归遍历换结点就行
class Solution {
public:
void swapNode(TreeNode* root) {
if (!root) {
return;
}
if (root->left || root->right) {
TreeNode* temp = root->left;
root->left = root->right;
root->right = temp;
}
if (root->left) {
swapNode(root->left);
}
if (root->right) {
swapNode(root->right);
}
}
TreeNode* mirrorTree(TreeNode* root) {
if (!root) {
return NULL;
}
swapNode(root);
return root;
}
};
剑指 Offer 28. 对称的二叉树
镜像分析其实就是每个结点的左子树和右子树相等,所以用递归很合适,递归出口左右为空(遍历完相等),左或右为空(一个还没遍历完就空不相等)。
class Solution {
public:
bool isEqual(TreeNode* p, TreeNode* q) {
if (!p && !q) {
return true;
}
if (!p || !q) {
return false;
}
return p->val == q->val && isEqual(p->left, q->right) && isEqual(p->right, q->left);
}
bool isSymmetric(TreeNode* root) {
return isEqual(root, root);
}
};
动态规划(简单)5.2
剑指 Offer 10- I. 斐波那契数列
看见直接递归,结果超时了,我是小丑,这边可以利用滚动数组,循环一遍就够了
class Solution {
public:
int fib(int n) {
if (n < 2) {
return n;
}
int p = 0;
int q = 0;
int r = 1;
for (int i = 2; i <= n; i++) {
p = q;
q = r;
r = (q + p) % 1000000007;
}
return r;
}
};
剑指 Offer 10- II. 青蛙跳台阶问题
也是个斐波那契,只不过求和而已。
class Solution {
public:
int numWays(int n) {
if (n < 2) {
return 1;
}
int q = 1;
int p = 1;
for (int i = 2; i <=n; i++) {
p = q + p;
q = p - q;
p = p % 1000000007;
}
return p;
}
};
剑指 Offer 63. 股票的最大利润
记录前面的最小数字,维护一个最大利益,返回就行
class Solution {
public:
int maxProfit(vector<int>& prices) {
int Min = INT_MAX;
int ans = 0;
for (int num : prices) {
Min = min(Min, num);
ans = max(num - Min, ans);
}
return ans;
}
};
动态规划(中等)
5.3
剑指 Offer 42. 连续子数组的最大和
动态规划真的拉的一批。
这个状态就是找直到nums[i]之前和的最大值,所以需要比对当前数字和之前和大小,最后比较之前的最大值
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int pre = 0;
int ans = nums[0];
for (int num : nums) {
pre = max(num + pre, num);
ans = max(ans, pre);
}
return ans;
}
};
剑指 Offer 47. 礼物的最大价值
比较右边和下边那个值大,直接加上当前值,最后返回数组右下角。
class Solution {
public:
int maxValue(vector<vector<int>>& grid) {
if (grid.size() == 0 || grid[0].size() == 0) {
return 0;
}
int m = grid.size();
int n = grid[0].size();
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(i == 0 && j > 0) {
grid[i][j] += grid[i][j - 1];
} else if(j == 0 && i > 0) {
grid[i][j] += grid[i - 1][j];
} else if(i > 0 && j > 0) {
grid[i][j] += max(grid[i - 1][j], grid[i][j - 1]);
}
}
}
return grid[m - 1][n - 1];
}
};
5.4
剑指 Offer 46. 把数字翻译成字符串
刚开始毫无思路,可以具体画一画,然后就会发现规律,其实和青蛙跳台阶很像,只不过需要加一个判断,判断当前的数字是否可以和前面的结合成组成字母的数字,也就是10-25。如果可以就把i - 1和i - 2的最大排列数加起来,如果不可以那么就说明最大排列数没变,这里使用滚动数组存最大值可以节省空间。
class Solution {
public:
int translateNum(int num) {
vector<int> tempNumber;
while (num) {
int i = num % 10;
tempNumber.push_back(i);
num = num / 10;
}
reverse(tempNumber.begin(), tempNumber.end());
int p = 1;
int q = 0;
int ans = 1;
if (tempNumber.size() > 1) {
int temp = tempNumber[0] * 10 + tempNumber[1];
if (temp > 9. && temp < 26) {
q = 2;
ans = q;
} else {
q = 1;
}
}
for (int i = 2; i < tempNumber.size(); i++) {
int temp = tempNumber[i - 1] * 10 + tempNumber[i];
if (temp > 9. && temp < 26) {
ans = p + q;
}
p = q;
q = ans;
}
return ans;
}
};
剑指 Offer 48. 最长不含重复字符的子字符串
本意是用哈希记录元素,维护哈希,莫名其名写成了暴力循环,我的评价是寄.
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if (!s.length()) {
return 0;
}
int start = 0;
int end = 0;
int ans = 1;
int temp = 0;
unordered_map<char, int> tempMap;
while (end < s.length()) {
tempMap[s[end]]++;
if (tempMap[s[end]] > 1) {
ans = max(end - start, ans);
start++;
end = start;
tempMap.clear();
tempMap[s[end]]++;
temp = 0;
}
temp++;
end++;
}
return ans > temp ? ans : temp;
}
};
学了学别的解法:
这个是理想的样子。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char,int> map;
int i = 0,j = 0,ans = 0;
while(j < s.size())
{
if (map.find(s[j]) != map.end()&&i <= map[s[j]]) // 如果这个窗口存在相同元素
i = map[s[j]] + 1; // 缩小窗口
if (j - i + 1 > ans) // 判断窗口的长度是否大于结果集
ans = j - i + 1; // 记录结果
map[s[j]] = j; // 覆盖元素位置
j++;
}
return ans;
}
};
双指针(简单)5.5.
剑指 Offer 18. 删除链表的节点
class Solution {
public:
ListNode* deleteNode(ListNode* head, int val) {
if (!head) {
return head;
}
if (head->val == val) {
return head->next;
}
ListNode* temp = head;
while (temp->next) {
if (temp->next->val == val) {
temp->next = temp->next->next;
break;
}
temp = temp->next;
}
return head;
}
};
剑指 Offer 22. 链表中倒数第k个节点
两个指针,一个快一个慢,快指针指到k+1的位置,然后两个同步走,fast走到末尾,slow就到了倒数第k个。
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode* fast = head;
ListNode* slow = head;
while (fast && k > 0) {
fast = fast->next;
k--;
}
while (fast) {
fast = fast->next;
slow = slow->next;
}
return slow;
}
};
双指针(简单)5.6
剑指 Offer 25. 合并两个排序的链表
递归就行
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == nullptr) {
return l2;
} else if (l2 == nullptr) {
return l1;
} else if (l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
} else {
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
};
剑指 Offer 52. 两个链表的第一个公共节点
这个哥们很深情,下面代码就参考这个哥们的。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == NULL || headB == NULL) {
return NULL;
}
ListNode* you = headA;
ListNode* me = headB;
while (you != me) {
you = you == NULL ? headB : you->next;
me = me == NULL ? headA : me->next;
}
return you;
}
};
5.7
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
两个指针遍历交换就行
class Solution {
public:
vector<int> exchange(vector<int>& nums) {
int i = 0;
int j = nums.size() - 1;
while (i < j) {
while (i < j && nums[i] % 2 == 1) {
i++;
}
while (i < j && nums[j] % 2 == 0) {
j--;
}
swap(nums[i], nums[j]);
}
return nums;
};
剑指 Offer 57. 和为s的两个数字
第一题两数之和,梦开始的地方,左右指针,比较大小移动。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
vector<int> ans;
while (left < right) {
if (nums[left] + nums[right] < target) {
left++;
} else if (nums[left] + nums[right] > target) {
right--;
} else {
ans.push_back(nums[left]);
ans.push_back(nums[right]);
break;
}
}
return ans;
}
};
搜索与回溯算法(中等)
前几天忙着考试断更了,回来继续打卡
剑指 Offer 12. 矩阵中的路径 5.23
思路:根据题干的意思,我们第一步应该找到单词的开头位置,然后从这个位置开始展开dfs,bool dfs(vector<vector<char>>& board, string& word, int k, int x, int y),
这是定义的函数声明,这里的k是对应word的位置,x,y是行和列,递归结束条件是什么呢,1,当前位置的字母和单词k位置的不一样。2,已经对比到了单词的末尾。
根据题意,移动方向只有上下左右四个位置,并且已经参与过对比的字母不能重复使用,所以我们每次比对的时候都需要将这个字母修改成特殊值,如果移动后的位置没有越界,就展开dfs,搜索完四个位置后,如果对比失败,在回溯时把刚刚我们修改的特殊值再改回来。重新找单词的开头重新搜索。
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
for (int i = 0; i < board.size(); i++) {
for (int j = 0; j < board[i].size(); j++) {
if (dfs(board, word, 0, i, j)) {
return true;
}
}
}
return false;
}
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; //方向数组
bool dfs(vector<vector<char>>& board, string& word, int k, int x, int y) {
// 递归结束条件,当前矩阵位置字母和单词k位置不相同
if (board[x][y] != word[k]) {
return false;
}
// 对比单词的最后一位,说明网格内存在完整的单词
if (k == word.size() - 1) {
return true;
}
// 取出当前位置的字符之后修改它,表示已经搜索过此位置,对应题目中字母只能使用一次
char t = board[x][y];
board[x][y] = '!';
for(int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
// 判读出界或者走到已经搜索过的位置
if(a < 0 || a >= board.size() || b < 0 || b >= board[0].size() || board[a][b] == '!') {
continue;
}
if(dfs(board, word, k + 1, a, b)) {
return true;
}
}
board[x][y] = t;
return false;
}
};
剑指 Offer 13. 机器人的运动范围
思路:这里就是判断矩阵中数的数位之和是否大于k,机器人可以往四个方向行走,很容易想到使用dfs,这里用一个二维数组来维护矩阵元素是否走过,这里的递归结束条件就是越界或着该位置遍历过以及数位和大于k,下面是代码:
class Solution {
public:
int numberSum(int x) {
int sum = 0;
while (x > 0) {
sum += x % 10;
x /= 10;
}
return sum;
}
int dfs(int m, int n, vector<vector<bool>> &temp, int k, int i, int j) {
if (i > m - 1 || j > n - 1 || i < 0 || j < 0 || numberSum(i) + numberSum(j) > k || temp[i][j] == true) {
return 0;
}
temp[i][j] = true;
return 1 + dfs(m, n, temp, k, i + 1, j) + dfs(m, n, temp, k, i - 1, j) + dfs(m, n, temp, k, i, j + 1) + dfs(m, n, temp, k, i, j - 1);
}
int movingCount(int m, int n, int k) {
vector<vector<bool>> test (m, vector<bool>(n));
return dfs (m, n, test, k, 0, 0);
}
};
剑指 Offer 34. 二叉树中和为某一值的路径 5.24
思路:这里就是需要一个二维数组来保存满足题意的数组来,利用先序遍历递归实现去遍历元素的每一个节点,记录路径的和,和target比较,如果满足则将当前数组存入二维数组,在每一层返回根节点时弹出数组的最后一位元素。
/**
* 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<vector<int>> ans;
vector<int> temp;
void dfs(TreeNode* root, int target) {
if (!root) {
return;
}
temp.emplace_back(root->val);
target -= root->val;
if (!root->left && !root->right && target == 0) {
ans.emplace_back(temp);
}
dfs(root->left, target);
dfs(root->right, target);
// 每一层返回时弹出临时数组的最后一个元素
temp.pop_back();
}
vector<vector<int>> pathSum(TreeNode* root, int target) {
dfs(root, target);
return ans;
}
};
emplace_back()
这里利用到了emplace_back()方法,对比一下其和push_back()的区别。
emplace_back()是c++11的新特性。
和push_back()的区别在于:
push_back()方法要调用构造函数和复制构造函数,这也就代表着要先构造一个临时对象,然后把临时的copy构造函数拷贝或者移动到容器最后面。
而emplace_back()在实现时,则是直接在容器的尾部创建这个元素,省去了拷贝或移动元素的过程。
剑指 Offer 36. 二叉搜索树与双向链表
思路:这个题看着挺唬人的,仔细看一下,其实就是利用中序遍历遍历搜索树,(中序遍历就可以得到一个递增序列),改变指针关系,画图就可以明白,每次都是保存上一个结点,到第二个结点时,让上一个结点的右指向当前结点,当点结点的左指向上一个尾结点,最后遍历完之后首位相连接。
class Solution {
public:
Node* treeToDoublyList(Node* root) {
if(root == nullptr) {
return nullptr;
}
dfs(root);
// 遍历完之后特殊处理,让头指向尾,尾指向头
head->left = pre;
pre->right = head;
return head;
}
Node *pre, *head;
void dfs(Node* temp) {
if(temp == nullptr) {
return;
}
dfs(temp->left);
if(pre != nullptr) {
// 保存的尾结点的右指针指向当前
pre->right = temp;
} else {
// 对第一次遍历到底部特殊处理,令head指向树中最小的
head = temp;
}
// 当前结点的左结点为尾结点
temp->left = pre;
// 更新尾结点
pre = temp;
dfs(temp->right);
}
};
剑指 Offer 54. 二叉搜索树的第k大节点
思路:一个中序遍历,第k个return
class Solution {
public:
int i = 0;
int val = 0;
int kthLargest(TreeNode* root, int k) {
if (!root) {
return 0;
}
kthLargest(root->right, k);
i++;
if (i == k) {
val = root->val;
return val;
}
kthLargest(root->left, k);
return val;
}
};
排序 5.25
剑指 Offer 45. 把数组排成最小的数
这个就是利用排序:
class Solution {
public:
string minNumber(vector<int>& nums) {
vector<string> strs;
string res;
for(int i = 0; i < nums.size(); i++) {
strs.push_back(to_string(nums[i]));
}
sort(strs.begin(), strs.end(), [](string& x, string& y){ return x + y < y + x; });
for(int i = 0; i < strs.size(); i++) {
res.append(strs[i]);
}
return res;
}
};
剑指 Offer 61. 扑克牌中的顺子
class Solution {
public:
bool isStraight(vector<int>& nums) {
sort(nums.begin(), nums.end());
int count_0 = 0;
for(int i = 0; i < 4; i++){
if(nums[i] == 0){
count_0++;
}
// 出现非0重复牌
else if(nums[i] == nums[i + 1]){
return false;
}
}
return nums[4] - nums[count_0] < 5;
}
};
剑指 Offer 40. 最小的k个数
开摆,sort加前k个
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
sort(arr.begin(), arr.end());
vector<int> ans;
for (int i = 0; i < k; i++) {
ans.emplace_back(arr[i]);
}
return ans;
}
};
搜索与回溯算法(中等)5.26
剑指 Offer 55 - I. 二叉树的深度
递归
class Solution {
public:
int maxDepth(TreeNode* root) {
if (!root) {
return 0;
}
return max(maxDepth(root->left) + 1, maxDepth(root->right) + 1);
}
};
剑指 Offer 55 - II. 平衡二叉树
思路:从上到下对比左右子树是否平衡
class Solution {
public:
int height(TreeNode* root) {
if (root == NULL) {
return 0;
} else {
return max(height(root->left), height(root->right)) + 1;
}
}
bool isBalanced(TreeNode* root) {
if (!root) {
return true;
} else {
return abs(height(root->left) - height(root->right)) > 1 ? false :true && isBalanced(root->left) && isBalanced(root->right);
}
}
};
还有一种从下到上的递归办法,这样可以只调用一次height函数,类似于后序遍历,如果底部有不符合条件的那么一直返回-1.
class Solution {
public:
int height(TreeNode* root) {
if (root == NULL) {
return 0;
}
int leftHeight = height(root->left);
int rightHeight = height(root->right);
if (leftHeight == -1 || rightHeight == -1 || abs(leftHeight - rightHeight) > 1) {
return -1;
} else {
return max(leftHeight, rightHeight) + 1;
}
}
bool isBalanced(TreeNode* root) {
return height(root) >= 0;
}
};
5.27
剑指 Offer 64. 求1+2+…+n
思路:这里可以用递归
class Solution {
public:
int sumNums(int n) {
return n == 0 ? 0 : n + sumNums(n - 1);
}
};
剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
思路:这里利用一下二叉搜索树的性质,比当前结点小的结点永远在这个结点的左子树,大的永远在右子树,如果题目给的两个结点一个在左,一个在右,或者说一个为本身,那么说明出现分叉,就是最近的祖先。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
TreeNode* ancestor = root;
while (ancestor) {
if (p->val < ancestor->val && q->val < ancestor->val) {
ancestor = ancestor->left;
} else if (p->val > ancestor->val && q->val > ancestor->val) {
ancestor = ancestor->right;
} else {
break;
}
}
return ancestor;
}
剑指 Offer 68 - II. 二叉树的最近公共祖先
思路:首先判断是根节点的几种可能,目标节点在根节点的两侧,其中一个目标节点为根节点,然后递归左子树和右子树,如果左右同时为空,说明目标节点不在左右两侧,所以最近公共祖先为root,当找到目标节点时,直接返回root,继续向上返回,如果左右都不会空,返回当前结点,如果一个为空返回另一个指,返回到上一次继续递归。
总结就是先找到当前结点,然后逐级返回,如果返回时在某个节点的左右就返回这个结点,知道返回到最后一层,如果找到不或者越界会像上一级返回NULL
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == NULL || p == root || q == root) {
return root;
}
TreeNode* l = lowestCommonAncestor(root->left, p, q);
TreeNode* r = lowestCommonAncestor(root->right, p, q);
return l == NULL ? r : (r == NULL ? l : root);
}
};
剑指 Offer 07. 重建二叉树 5.29
思路:利用前序中序遍历的性质,首先在前序中找到根节点,那么在中序遍历中,根节点的左边一定是他的左子树,右边是右子树,然后根据这个特点,对左和右子树继续进行分治。最后就可以得到返回结点,这里的核心方法肯定还是递归。
在中序遍历中对根节点进行定位时,一种简单的方法是直接扫描整个中序遍历的结果并找出根节点,但这样做的时间复杂度较高。我们可以考虑使用哈希表来帮助我们快速地定位根节点。对于哈希映射中的每个键值对,键表示一个元素(节点的值),值表示其在中序遍历中的出现位置。在构造二叉树的过程之前,我们可以对中序遍历的列表进行一遍扫描,就可以构造出这个哈希映射。在此后构造二叉树的过程中,我们就只需要 O(1) 的时间对根节点进行定位了。
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
this->preorder = preorder;
// 利用hashMap映射索引,可以在每一次取的时候时间复杂度为o(1)
for(int i = 0; i < inorder.size(); i++) {
dic[inorder[i]] = i;
}
return recur(0, 0, inorder.size() - 1);
}
// 全局变量,方便在后续递归时使用。
vector<int> preorder;
unordered_map<int, int> dic;
// 通过下面参数锁定遍历划分的范围,含义下面会讲。
TreeNode* recur(int root, int left, int right) {
if(left > right) { // 已经越过叶子结点.
return nullptr;
}
TreeNode* node = new TreeNode(preorder[root]); // 建立根节点
int i = dic[preorder[root]]; // 获取当前结点在中序的index
// 三个参数的含意
// root:是根节点在前序遍历的位置
// left:在中序遍历中左边界
// 递归左子树时,左边界限是确定的,i - 1是右边界,因为i是中序的索引,他的左子树的右边界一定在他的上一位。
node->left = recur(root + 1, left, i - 1); // 开启左子树递归
// 递归右子树时,右边界确定,i + 1是左边界,理由同上,这里的root计算实际就是在前序中加上左子树的长度加一找到右子树根结点。
node->right = recur(root + i - left + 1, i + 1, right); // 开启右子树递归
return node; // 回溯返回根节点
}
};