力扣剑指offer第2版简单题
- 数组中重复的数字
- 替换空格
- 从尾到头打印链表
- 用两个栈实现队列
- 斐波那契数列
- 青蛙跳台阶问题
- 旋转数组的最小数字
- 二进制中1的个数
- 打印从1到最大的n位数
- 删除链表的节点
- 调整数组顺序使奇数位于偶数前面
- 链表中倒数第K个节点
- 反转链表
- 合并两个排序的链表
- 二叉树的镜像
- 对称的二叉树
- 顺时针打印矩阵
- 包含min函数的栈
- 从上到下打印二叉树
- 数组中出现次数超过一半的数字
- 最小的K个数
- 连续子数组的最大和
- 第一个只出现一次的字符
- 两个链表的第一个公共节点
- 在排序数组中查找数字,统计出现的次数
- 0--n-1中缺失的数字
- 二叉搜索树的第K大节点
- 二叉树的深度
- 平衡二叉树
- 和为s的两个数字
- 翻转单词顺序
- 左旋转字符串
- 扑克牌中的顺子
- 圆圈中最后剩下的数字
- 不用加减乘除做加法
- 二叉搜索树最近公共祖先
- 二叉树的最近公共祖先
数组中重复的数字
链接: 数组中重复的数字.
- 先排序,再找重复值
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 -1; // 数组中的值在0~n-1范围,所以可以返回-1,代表没有重复值
}
};
- 哈希集合
class Solution
{
public:
int findRepeatNumber(vector<int>& nums)
{
unordered_map<int, bool> map;
for(int num : nums) // 遍历nums,看num是否在nums里面
{
if(map[num]) // 如果存在,返回num
return num;
map[num] = true;
}
return -1;
}
};
替换空格
class Solution
{
public:
string replaceSpace(string s)
{
string str;
for(int i = 0; i<s.size();++i)
{
if(s[i] == ' ')
{
str.append("%20");
}
else
{
str.push_back(s[i]);
}
}
return str;
}
};
从尾到头打印链表
- 就地逆序数组
class Solution
{
public:
vector<int> reversePrint(ListNode* head)
{
vector<int> result;
while (head)
{
result.push_back(head->val);
head = head->next;
}
int len = result.size();
for (int i = 0; i < len / 2; i++)
{
swap(result[i], result[len-i-1]);
}
return result;
}
};
- 使用栈
class Solution
{
public:
vector<int> reversePrint(ListNode* head)
{
vector<int> result;
stack<int> s;// 创建一个栈
while(head) // 先把链表都压入栈,然后再取出来
{
s.push(head->val);
head = head->next;
}
while(!s.empty())
{
result.push_back(s.top());
s.pop();
}
return result;
}
};
- 反转链表,再遍历
class Solution
{
public:
vector<int> reversePrint(ListNode* head)
{
vector<int> result; // 定义一个数组
if (head == nullptr) // 空链表
return result;
// 反转链表
ListNode *curr = head->next; // 先记住头结点的下一个结点
ListNode *temp;
head->next = nullptr;
while(curr)
{
temp = curr->next;
curr->next = head;
head = curr; // 先放头
curr = temp;
}
// 取出链表中的值
while(head)
{
result.push_back(head->val);
head = head->next;
}
return result;
}
};
用两个栈实现队列
- 维护两个栈,第一个用来插入,第二个用来删除
class CQueue
{
stack<int> stack1,stack2; // 新建两个栈
public:
CQueue() // 用两个栈实现队列操作
{
while (!stack1.empty()) // 先清空栈
{
stack1.pop();
}
while (!stack2.empty())
{
stack2.pop();
}
}
void appendTail(int value) // 入队
{
stack1.push(value);
}
int deleteHead() // 出队
{
// 如果第二个栈为空
if (stack2.empty())
{
while (!stack1.empty())
{
stack2.push(stack1.top()); // 先将1栈的数挪到2栈里,
stack1.pop(); // 1 栈清空
}
}
if (stack2.empty()) // 如果 2 栈还为空,有可能放进去的也是空
{
return -1;
}
else // 挪数据完毕,开始从 2 栈里删除
{
int deleteItem = stack2.top();
stack2.pop();
return deleteItem;
}
}
};
斐波那契数列
- 动态规划,使用滚动数组节省空间
class Solution
{
public:
int fib(int n)
{
int MOD = 1000000007;
if (n < 2)
{
return n;
}
int p = 0, q = 0, r = 1; // p:缓冲位置,q:参与计算的值1,r: 参与计算的值2
for (int i = 2; i <= n; ++i)
{
p = q;
q = r;
r = (p + q)%MOD;
}
return r;
}
};
青蛙跳台阶问题
- 与斐波那契数列相似,只不过斐波那契数列中,f(0) = 0,f(1) = 1,青蛙跳台,f(0) = 1, f(1) = 1
class Solution {
public:
int numWays(int n)
{
int MOD = 1000000007;
if(n<2)
{
return 1;
}
int p = 0, q = 1, r = 1;
for(int i = 2; i<=n; ++i)
{
p = q;
q = r;
r = (p+q)%MOD;
}
return r;
}
};
旋转数组的最小数字
- 二分查找
时间复杂度 O(log(n))
class Solution
{
public:
int minArray(vector<int>& numbers)
{
int low = 0;
int high = numbers.size() - 1;
while(low < high)
{
int priot = low + (high - low) / 2;
if(numbers[priot] < numbers[high])
{
high = priot;
}
else if(numbers[priot] > numbers[high])
{
low = priot + 1;
}
else
{
high -= 1;
}
}
return numbers[low];
}
};
二进制中1的个数
- // 循环检查二进制位
// 思路及解法
// 我们可以直接循环检查给定整数 n 的二进制位的每一位是否为 1。
// 具体代码中,当检查第 i 位时,我们可以让 n 与 2^i 进行与运算,当且仅当 n 的第 i 位为 1 时,运算结果不为 0。
class Solution
{
public:
int hammingWeight(uint32_t n)
{
int ret = 0; // 记录1的个数
for (int i = 0; i < 32; i++)
{
if (n & (1 << i))
{
ret++;
}
}
return ret;
}
};
- // 位运算优化
// n & (n-1): 其运算结果恰为把n的二进制位中的最低位 1 变为 0 之后的结果
// 在实际代码中,不断让当前的 n 与 n-1 做与运算,直到 n 变为 0。因为每次运算会使得 n 的最低位1被翻转
// 因此运算次数相当于 n 的二进制中 1 的个数
class Solution
{
public:
int hammingWeight(uint32_t n)
{
int ret = 0;
while (n)
{
n &= n - 1;
ret++;
}
return ret;
}
};
打印从1到最大的n位数
- 不考虑大数越界情况
class Solution
{
public:
vector<int> printNumbers(int n)
{
vector <int> ans;
int end = pow (10,n); //10^n
for (int i = 1;i < end;i++)
{
ans.push_back (i);
}
return ans;
}
};
删除链表的节点
class Solution
{
public:
ListNode* deleteNode(ListNode* head, int val)
{
if(head->val == val)
{
return head->next;
}
ListNode *pre = head, *cur = head->next;
while(cur != nullptr && cur->val != val)
{
pre = cur;
cur = cur->next;
}
if(cur != nullptr)
{
pre->next = cur->next;
}
return head;
}
};
调整数组顺序使奇数位于偶数前面
class Solution
{
public:
vector<int> exchange(vector<int>& nums)
{
int i = 0, j = nums.size() - 1; // i 从左向右找奇数,j 从右向左找 偶数
while (i < j) // 当i == j 时跳出循环
{
// nums[i] & 1 等价于 nums[i] % 2,都是求余数
while(i < j && (nums[i] & 1) == 1) // 找到奇数
{
i++;
}
while(i < j && (nums[j] & 1) == 0) // 找到偶数
{
j--;
}
swap(nums[i], nums[j]); // 二者交换
}
return nums;
}
};
链表中倒数第K个节点
- 假设有 n 个节点,倒数第 k 个节点,相当于整数第 n-k 个节点
顺序遍历到第 n-k 个节点,返回即可
class Solution
{
public:
ListNode* getKthFromEnd(ListNode* head, int k)
{
int n = 0; // 链表长度
ListNode* node = nullptr; // 定义空节点遍历链表,计算长度
for (node = head; node; node = node->next) // 先计算出链表的长度
{
n++;
}
for (node = head; n > k; n--)
{
node = node->next;
}
return node;
}
};
- // 双指针,快慢指针
class Solution
{
public:
ListNode* getKthFromEnd(ListNode* head, int k)
{
ListNode* fast = head; // 先让快慢指针都指向头节点
ListNode* slow = head;
while (fast && k > 0) // 让快指针先走 k 步,slow 和 fast 刚好相差 k
{
fast = fast->next;
k--;
}
while (fast) // 当 fast 为空节点即遍历到链表最后节点的下一个节点时,跳出循环
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
};
反转链表
class Solution
{
public:
ListNode* reverseList(ListNode* head)
{
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr)
{
ListNode* next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
};
合并两个排序的链表
- 双指针
class Solution
{
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{
ListNode* yummy=new ListNode(0),*cur=yummy;
while(l1 && l2) // 任意一个链表为空时跳出循环
{
if(l1->val<=l2->val) // 谁小就往合并链表里
{
cur->next=l1;
l1=l1->next;
}
else
{
cur->next=l2;
l2=l2->next;
}
cur=cur->next;
}
if(l1) cur->next=l1;
if(l2) cur->next=l2;
return yummy->next;
}
};
二叉树的镜像
class Solution
{
public:
TreeNode* mirrorTree(TreeNode* root)
{
if (root == nullptr)
{
return nullptr;
}
TreeNode* tmp = root->left; // 暂存 root 的左子节点
root->left = mirrorTree(root->right); // 开启递归 root 的右子节点,并将返回值作为 root 的左子节点
root->right = mirrorTree(tmp); // 开启递归左子节点,并将返回值作为 root 的右子节点
return root;
}
};
对称的二叉树
class Solution {
public:
bool check(TreeNode *p, TreeNode *q)
{
if(!p && !q) return true; // 树为空返回true
if(!p || !q) return false;
return p->val == q->val && check(p->left,q->right) && check(p->right,q->left);
}
bool isSymmetric(TreeNode* root)
{
return check(root,root);
}
};
顺时针打印矩阵
class Solution
{
public:
vector<int> spiralOrder(vector<vector<int>>& matrix)
{
vector <int> ans; // 新建数组,存放输出的结果
if(matrix.empty()) //若矩阵为空,直接返回答案
{
return ans;
}
int u = 0; //赋值上下左右边界
int d = matrix.size() - 1;
int l = 0;
int r = matrix[0].size() - 1;
while(true)
{
for(int i = l; i <= r; ++i) //向右移动直到最右
{
ans.push_back(matrix[u][i]);
}
if(++ u > d) //重新设定上边界,若上边界大于下边界,则遍历遍历完成,下同
{
break;
}
for(int i = u; i <= d; ++i) //向下
{
ans.push_back(matrix[i][r]);
}
if(-- r < l) //重新设定有边界
{
break;
}
for(int i = r; i >= l; --i)//向左
{
ans.push_back(matrix[d][i]);
}
if(-- d < u) //重新设定下边界
{
break;
}
for(int i = d; i >= u; --i)//向上
{
ans.push_back(matrix[i][l]);
}
if(++ l > r) //重新设定左边界
{
break;
}
}
return ans;
}
};
包含min函数的栈
- 辅助栈
class MinStack {
stack<int> x_stack;
stack<int> min_stack;
public:
MinStack() {
min_stack.push(INT_MAX);
}
void push(int x) {
x_stack.push(x);
min_stack.push(std::min(min_stack.top(), x)); // 使用std:: ,这样才能调用系统的min函数,要不会和下面的min函数冲突
}
void pop() {
x_stack.pop();
min_stack.pop();
}
int top() {
return x_stack.top();
}
int min() {
return min_stack.top();
}
};
从上到下打印二叉树
class Solution
{
public:
vector<vector<int>> levelOrder(TreeNode* root)
{
/* 根据函数返回值定义存储结果的变量 */
vector<vector<int>> result;
/* 定义一个队列用于存储节点的数据 */
queue<TreeNode*> que;
/* 判空 */
if(root != NULL)
{
que.push(root);
}
/* 开始层序遍历 */
while(!que.empty())
{
/* 计算队列的大小也即有多少个孩子 */
int size = que.size();
/* 定义一个临时vector 存储每一层 */
vector<int> vec;
/* 层序遍历 */
for(int i = 0; i < size; i++)
{
/* 获取第一个节点数据 */
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if(node->left != NULL)
{
que.push(node->left);
}
if(node->right != NULL)
{
que.push(node->right);
}
}
/* 将一层的数据保存 */
result.push_back(vec);
}
return result;
}
};
数组中出现次数超过一半的数字
- 排序
class Solution
{
public:
int majorityElement(vector<int>& nums)
{
sort(nums.begin(),nums.end());
return (nums[nums.size()/2]);
}
};
- 哈希表
class Solution
{
public:
int majorityElement(vector<int>& nums)
{
unordered_map<int, int> counts;
int majority = 0, cnt = 0;
for (int num: nums) // 遍历数组
{
++counts[num];
if (counts[num] > cnt)
{
majority = num;
cnt = counts[num];
}
}
return majority;
}
};
最小的K个数
- 函数自带的排序,再将最小的K个数输出
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k)
{
vector<int> nums;
sort(arr.begin(),arr.end());
// for(int i = 0;i<k;++i)
// {
// nums.push_back(arr[i]);
// }
nums.assign(arr.begin(),arr.begin()+k); // 将arr从begin--begin+k的数拷贝给nums
return nums;
}
};
- 快速排序
class Solution
{
public:
vector<int> getLeastNumbers(vector<int>& arr, int k)
{
quickSort(arr, 0, arr.size() - 1);
vector<int> res;
res.assign(arr.begin(), arr.begin() + k);
return res;
}
private:
void quickSort(vector<int>& arr, int l, int r)
{
// 子数组长度为 1 时终止递归
if (l >= r) return;
// 哨兵划分操作(以 arr[l] 作为基准数)
int i = l, j = r;
while (i < j)
{
while (i < j && arr[j] >= arr[l]) j--;
while (i < j && arr[i] <= arr[l]) i++;
swap(arr[i], arr[j]);
}
swap(arr[i], arr[l]);
// 递归左(右)子数组执行哨兵划分
quickSort(arr, l, i - 1);
quickSort(arr, i + 1, r);
}
};
连续子数组的最大和
- 动态规划
class Solution
{
public:
int maxSubArray(vector<int>& nums)
{
int pre = 0;
int maxAns = nums[0]; // 存放最大值
for (const auto &x: nums) // 遍历 nums 数组
{
pre = max(pre + x, x); // 前 i 项和,与 第 i 项相比,取最大值
maxAns = max(maxAns, pre); // 保存或更新已找到的最大值
}
return maxAns;
}
};
第一个只出现一次的字符
- 哈希表
class Solution {
public:
char firstUniqChar(string s)
{
unordered_map<int, int> frequency;
for (char ch: s)
{
++frequency[ch];
}
for (int i = 0; i < s.size(); ++i)
{
if (frequency[s[i]] == 1)
{
return s[i];
}
}
return ' ';
}
};
两个链表的第一个公共节点
- 双指针
class Solution
{
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
if(headA == nullptr || headB == nullptr)
{
return nullptr;
}
ListNode *pA = headA, *pB = headB;
while(pA != pB)
{
pA = pA == nullptr ? headB : pA->next;
pB = pB == nullptr ? headA : pB->next;
}
return pA;
}
};
在排序数组中查找数字,统计出现的次数
- // 暴力
class Solution {
public:
int search(vector<int>& nums, int target)
{
int ans = 0;
for(int x: nums)
{
if(x == target)
{
ans++;
}
}
return ans;
}
};
- // 二分查找
class Solution {
public:
int binarySearch(vector<int>& nums, int target, bool lower)
{
int left = 0;
int right = (int)nums.size()-1;
int ans = (int)nums.size(); // 用于存放返回数据的下标,初始为数组大小
while(left <= right)
{
int mid = (left + right) / 2;
// 如果 lower 为true,则查找第一个大于等于target的下标,否则查找第一个大于target下标
if(nums[mid] > target || (lower && nums[mid] >= target))
{
right = mid -1;
ans = mid;
}
else
{
left = mid + 1;
}
}
return ans;
}
int search(vector<int>& nums, int target)
{
// 如果 lower 为 true,则查找第一个大于等于 target 的下标,
// 否则查找第一个大于 target 的下标。
int leftIdx = binarySearch(nums,target,true);
int rightIdx = binarySearch(nums,target,false) - 1;
if(leftIdx<=rightIdx && rightIdx < nums.size() && nums[leftIdx] == target && nums[rightIdx] == target)
{
return rightIdx - leftIdx + 1;
}
return 0;
}
};
0–n-1中缺失的数字
class Solution
{
public:
int missingNumber(vector<int>& nums)
{
// 二分法,如果中间的数num【i】不等于坐标i就说明缺失的数值在这个位置或者前面
int left = 0, right = nums.size() - 1;
int ans = nums.size();
//初始化要注意,表示最后一个n-1缺失,这时候数组的坐标i都和num【i】相同
while(left <= right)
{
int mid = left + (right - left) / 2; // 这种方式可以防止溢出
if(nums[mid] != mid)
{
right = mid - 1;
ans = mid;
}
else // nums[mid] != mid,此时 mid, left 和 right 到达同一位置
{
left = mid + 1;
}
}
return ans;
}
};
二叉搜索树的第K大节点
- // 二叉树,根节点的左节点比根节点小,根节点的右节点比根节点大
// 二叉树的中序遍历为递增序列
// 中序遍历(左,根,右)的倒序(右,根,左)则为递减序列,则第k大则为倒序的第K个节点
class Solution
{
public:
int count = 0, res = 0; // count 用来计数我们在降序的数字序列中走了几位,当走了k位时,让res等于此时的值
int kthLargest(TreeNode* root, int k)
{
inorder(root, k);
return res;
}
void inorder(TreeNode* root, int k) // 中序遍历的倒序
{
if (!root) // 空树
{
return;
}
inorder(root -> right, k); // 右
++ count;
if (count == k)
{
res = root -> val;
return;
}
inorder(root -> left, k); // 左
}
};
二叉树的深度
class Solution {
public:
int maxDepth(TreeNode* root)
{
if(!root)
{
return 0;
}
return max(maxDepth(root->left), maxDepth(root->right))+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; // 说明不是平衡二叉树
}
return max(leftHeight,rightHeight)+1;
}
bool isBalanced(TreeNode* root)
{
return height(root)>=0; // 返回bool型
}
};
和为s的两个数字
- // 双指针
class Solution
{
public:
vector<int> twoSum(vector<int>& nums, int target)
{
int i = 0; // 左指针
int j = nums.size() - 1; // 右指针
while(i<j)
{
if(nums[i] + nums[j] == target)
{
return {nums[i],nums[j]};
}
else if(nums[i] +nums[j] > target)
{
j--;
}
else
{
i++;
}
}
return {};
}
};
翻转单词顺序
class Solution
{
public:
string reverseWords(string s)
{
int i = s.size() - 1;
string ans;
while(i >= 0) // 用来遍历 s
{
int c = 0;
while(i >= 0 && s[i] == ' ') // 用来遍历空格
{
i--;
}
while(i >= 0 && s[i] != ' ') // 用来遍历我们的字符
{
i--;
c++;
}
if(c)
{ // substr 返回由 i+1 开始的 c 个字符组成的字符串
ans += s.substr(i+1, c) + " "; // 返回一个单词,后面紧跟一个空格分隔
}
}
return ans.substr(0, ans.size()-1); // 减1是减去最后的一个空格
}
};
左旋转字符串
class Solution {
public:
string reverseLeftWords(string s, int n)
{
return s.substr(n,s.size()) + s.substr(0,n);
}
};
扑克牌中的顺子
- // 排序+遍历
class Solution
{
public:
bool isStraight(vector<int>& nums)
{
int joker = 0; // 统计大小王数量
sort(nums.begin(),nums.end()); // 排序
for(int i = 0;i < 4;i++)
{
if(nums[i] == 0) // 统计大小王数量
{
joker++;
}
else if(nums[i] == nums[i+1]) // 若有重复,提前返回 false
{
return false;
}
}
return nums[4] - nums[joker] < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
}
};
- 哈希集合+遍历
class Solution {
public:
bool isStraight(vector<int>& nums)
{
unordered_map<int, bool> map;
int mi = 14;
int ma = 0;
for(int num: nums)
{
if(num == 0) // 遇到大小王,跳过,进行下一次循环
continue;
ma = max(ma,num); // 更新最大值和最小值
mi = min(mi,num);
if(map[num]) // 有重复的就不是顺子
return false;
map[num] = true; //
}
return ma - mi < 5;
}
};
圆圈中最后剩下的数字
- // 约瑟夫环
// 动态规划:f(n) = (f(n-1)+m)%n
n 个数,每次删除第m个
class Solution
{
public:
int lastRemaining(int n, int m)
{
int x = 0; // f(1) = 0 恒成立
for (int i = 2; i <= n; i++)
{
x = (x + m) % i;
}
return x;
}
};
不用加减乘除做加法
- // 位运算
// 非进位和 + 进位项
// 非进位和:a ^ b (a 异或 b) ,异或是无进位相加
// 进位项: a & b << 1
class Solution
{
public:
int add(int a, int b)
{
while(b != 0) // 当进位为 0 是跳出
{
int c = (unsigned int)(a & b) << 1; // c = 进位 ,,c++不支持负值左移,所以转化为无符号整型
a ^= b; // a = 非进位和
b = c; // b = 进位
}
return a;
}
};
二叉搜索树最近公共祖先
- // 一次遍历
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
{
TreeNode* ancestor = root; // 当前节点
while (true)
{
if (p->val < ancestor->val && q->val < ancestor->val) // p 和 q 的值都小于当前节点,p和q可能在当前节点的左子树里
{
ancestor = ancestor->left;
}
else if (p->val > ancestor->val && q->val > ancestor->val) // p 和 q 的值都大于当前节点,p 和 q 可能在当前节点的右子树里
{
ancestor = ancestor->right;
}
else // 否则,p q,要么是当前节点的左右子树,要么有一个就是当前节点
{
break;
}
}
return ancestor;
}
};
二叉树的最近公共祖先
- // 二叉树和二叉搜索树的区别:
// 二叉搜索树中:左子树 < 根节点 < 右子树
// 二叉树没有这个性质
class Solution
{
public:
TreeNode* ans;
bool dfs(TreeNode* root, TreeNode* p, TreeNode* q)
{
if (root == nullptr)
{
return false;
}
bool lson = dfs(root->left, p, q);
bool rson = dfs(root->right, p, q);
if ((lson && rson) || ((root->val == p->val || root->val == q->val) && (lson || rson))) // 要么两个节点在是自己的左右子树,要么本身是两个节点其中一个,只有找到最近的公共祖先才会进入此判断
{
// lson && rson: 左子树和右子树均包含 p 节点或 q 节点
// 如果左子树包含的是 p 节点,那么右子树只能包含 q 节点
ans = root; // 返回找到最近的公共祖先
}
return lson || rson || (root->val == p->val || root->val == q->val);
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
{
dfs(root, p, q);
return ans;
}
};