文章目录
- 一、数据结构
- 二、算法
- LeetCode Top 100
- 1、两数之和
- 53、最大子数组和
- 338、比特位计数
- 5、最长回文子串
- 543、二叉树的直径
- 461、汉明距离
- 448、找到所有数组中消失的数字
- 101、对称二叉树
- 104、二叉树的最大深度
- 226、翻转二叉树
- 234、回文链表
- 160、相交链表
- 155、最小栈
- 96、不同的二叉搜索树
- 406、[根据身高重建队列](https://leetcode-cn.com/problems/queue-reconstruction-by-height/)
- 287. [寻找重复数](https://leetcode-cn.com/problems/find-the-duplicate-number/)
- 347. [前 K 个高频元素](https://leetcode-cn.com/problems/top-k-frequent-elements/)
- 四、面试过题目
本文根据B站饲养员老师算法面试题视频讲解,整理了C++版本代码。
同时将LeetCode Top 100题目也整理到归类中。尽量选择易于理解和简洁的代码实现。因为面试准备记录时间仓促,疏漏之处请谅解。
一、数据结构
0、排序算法(时间复杂度:空间复杂度)
算法面试题目不属于饲养员视频和Top 100内容,但面试时常会问到,根据十大排序算法文章(B站和CSDN)上都有动图版本,整理的四种算法也是易于理解的版本。
1)冒泡排序(T:O(n2);S:O(1))
- 后面大值排好序,j<n-i-1
void bubbleSort(int a[], int n)
{
for(int i =0 ; i< n-1; ++i) {
for(int j = 0; j < n-i-1; ++j) {//再次进入本层for循环,第i个已经拍好序
if(a[j] > a[j+1]) {
int tmp = a[j] ; //交换可用swap函数
a[j] = a[j+1] ;
a[j+1] = tmp;
}
}
}
}
2)选择排序(T:O(n2);S:O(1))
- 找出最小值交换
function selectionSort(arr) {
var len = arr.length;
var minIndex, temp;
for (var i = 0; i < len - 1; i++) {
minIndex = i;// 将索引i保存,将arr[i]作为最小值向后查找
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { // 寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
temp = arr[i];//将最小索引值和i进行交换,可用加判断条件再交换
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
3)插入排序(T:O(n2);S:O(1))
- 新插入点挪动到指定位置
void StraightSort(int *arr,int len)
{
int tmp;
int i;
int j;
for (i = 1;i < len;i++) {
tmp = arr[i];
for (j = i - 1;j >= 0 && arr[j] > tmp;j--)//从i的前一个位置开始
arr[j + 1] = arr[j];//发现打的往后挪
arr[j + 1] = tmp;//插入到新位置
}
}
4)快速排序(T:O(n*logn);S:O(logn))
- 分割点,i 计为小值个数(可能存在原地移动),两层交换
void quick_sort(data_type_t *A, int p, int r){
if(p >= r){
return;
}
int q = partision(A, p, r);
quick_sort(A, p, q - 1);
quick_sort(A, q + 1, r);
}
static int partision(data_type_t *A, int p, int r){
data_type_t x = A[r]; //以最后一个元素作为主元
int i = p - 1;
int j = p;
while(j < r){
if(A[j] < x){
i++;//**比主元小的挪动次数**
swap(&A[j], &A[i]); //比主元大的数放右边,小的数放在数组左边。
}
j++;
}
swap(&A[++i], &A[r]); //主元作为分割点
return i; //返回当前分割点位置
}
4-1)TopK问题是快排做改造(L215)
- 分割点,主函数循环逼近;i 计为小值个数(可能存在原地移动),两层交换
题目:给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int len = nums.size();
int left = 0;
int right = len - 1;
int target = len - k;// 转换一下,第 k 大元素的下标是 len - k
while (true) {
int index = partition(nums, left, right);
if (index == target) {
return nums[index];
} else if (index < target) {
left = index + 1;
} else {
right = index - 1;
}
}
}
// 分割函数是一样的,减少理解工作量
int partition(vector<int>& nums, int p, int r){
int x = nums[r]; //以最后一个元素作为主元
int i = p - 1;
int j = p;
while(j < r){
if(nums[j] < x){
i++;//**比主元小的挪动次数**
swap(nums[j], nums[i]); //比主元大的数放右边,小的数放在数组左边。
}
j++;
}
swap(nums[++i], nums[r]); //主元作为分割点
return i; //返回当前分割点位置
}
5)归并排序(T:O(n*logn);S:O(n))
- 左右归并递归,合并(有序合并三分)
private static void mergesort(int[] array, int left, int right) {
int mid=(left+right)/2;
if(left<right) {
mergesort(array, left, mid);
mergesort(array, mid+1, right);
merge(array, left,mid, right);// 递归认为左右已经排好序!!!
}
}
private static void merge(int[] array, int l, int mid, int r) {
int lindex=l;int rindex=mid+1;
int team[]=new int[r-l+1];
int teamindex=0;
while (lindex<=mid&&rindex<=r) {//先左右比较合并
if(array[lindex]<=array[rindex]) {
team[teamindex++]=array[lindex++];
}
else {
team[teamindex++]=array[rindex++];
}
}
while(lindex<=mid) {//当一个越界后剩余按序列添加即可
team[teamindex++]=array[lindex++];
}
while(rindex<=r) {
team[teamindex++]=array[rindex++];
}
for(int i=0;i<teamindex;i++) {
array[l+i]=team[i];
}
}
6) 桶排序(T:O(n);S:O(m))
- 最大最小值,计算桶个数,分桶,小桶排序,装桶
#include <iostream>
#include <vector>
using namespace std;
void bucketSort(int data[], int len)
{
//找到数组最大最小值
int min = data[0];
int max = min;
for(int i = 1; i < len; i++) {
if(data[i] < min)
min = data[i];
if(data[i] > max)
max = data[i];
}
//计算桶数量
int bucketCounts = (max-min)/len + 1;
vector<vector<int>> bucketArrays;
for(int i = 0; i < bucketCounts; i++) {
//position 0 used to keep the data count store in this bucket
vector<int> bucket;
bucketArrays.push_back(bucket);
}
//将数据分桶
for(int j = 0; j < len; j++) {
int num = (data[j]-min)/len;
bucketArrays[num].push_back(data[j]);
}
//桶内排序
for(int i = 0; i < bucketCounts; i++)
std::sort(bucketArrays[i].begin(), bucketArrays[i].end());
//将桶内数据依次合并
int index = 0;
for(int k = 0; k < bucketCounts; k++) {
for(int s = 0; s < bucketArrays[k].size(); s++) {
data[index++] = bucketArrays[k][s];
}
}
}
int main() {
int arr[10] = {18,11,28,45,23,50};
bucketSort(arr, 6);
for(int i = 0; i < 6; i++)
cout << arr[i] << endl;
return 0;
}
原文链接:https://blog.csdn.net/sinat_31275315/article/details/107868240
1、数组
1)最大连续1个数(L485)
- 循环计数,找最大值,可能队尾
题目:给定一个二进制数组 nums , 计算其中最大连续 1 的个数。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/majority-element
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
public:
int findMaxConsecutiveOnes(vector<int>& nums) {
int maxCount = 0, count = 0;
int n = nums.size();
for (int i = 0; i < n; i++) {
if (nums[i] == 1) {
count++;
} else {
maxCount = max(maxCount, count);
count = 0;
}
}
maxCount = max(maxCount, count);
return maxCount;
}
};
2)移动零(L283)
- 不为0元素归入数组,后续补零
题目:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int index = 0;
for(int i = 0;i<nums.size();i ++){
if(nums[i] !=0){
nums[index] = nums[i];
index++;
}
}
for(int i = index;i<nums.size();i ++){
nums[i] = 0;
}
}
};
3)删除指定元素(L27)
- 符合和不符合元素交换
题目:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
if(nums.size() == 0){
return 0;
}
int l = 0;
int r = nums.size() -1;
while(l < r){
while(l < r && nums[l] != val){
l++;
}
while(l < r && nums[r] == val){
r--;
}
int tmp = nums[l];
nums[l] = nums[r];
nums[r] = tmp;
}
if(nums[l] == val){
return l;
}
else{
return l +1;
}
}
};
3)下一个排列(L31)
- 利用swap,reverse,sort等函数
题目:整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int len = nums.size();
int i = len -2;//倒叙找第一个小的n[i]
while(i >= 0 && nums[i] >= nums[i+1])
i --;
if(i >=0){
int j = len -1;//其余里找个大于n[i]
while(j >= 0 && nums[i] >= nums[j])
j--;
swap(nums[i], nums[j]);
}
reverse(nums.begin()+i+1,nums.end());//对n[i]后排序
}
};
4)旋转图像(L48)
- 水平+对角线反转,要画图
题目:给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
// 水平翻转
for (int i = 0; i < n / 2; ++i) {
for (int j = 0; j < n; ++j) {
swap(matrix[i][j], matrix[n - i - 1][j]);
}
}
// 主对角线翻转
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
swap(matrix[i][j], matrix[j][i]);
}
}
}
};
2、链表
1)插入节点
struct Node *insert(struct Node *head, int ind, struct Node *a) {
struct Node ret, *p = &ret;
ret.next = head;
// 从虚拟头节点开始向后走 ind 步
while (ind--) p = p->next;
// 完成节点的插入操作
a->next = p->next;
p->next = a;
// 返回真正的链表头节点地址
return ret.next;
}
2)删除元素(L203)
题目:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//创建一个虚拟头结点
ListNode* dummyNode=new ListNode(0);
dummyNode->next=head;
ListNode* prev=dummyNode;
//确保当前结点后还有结点
while(prev->next!=NULL){
if(prev->next->val==val){
prev->next=prev->next->next;
}else{
prev=prev->next;
}
}
return dummyNode->next;
}
};
3)删除链表的倒数第 N 个结点(L19)
- 先取长度,再删除
题目:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
class Solution {
public:
int getLength(ListNode* head){
int length = 0;
while(head){
length++;
head = head->next;
}
return length;
}
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* cur = dummy;
int length = getLength(head);
for(int i = 0;i<length-n;i++)
cur = cur->next;
cur->next = cur->next->next;
return dummy->next;
}
};
4)链表反转(L206)
- 前后节点保存+移动
题目:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* prev = NULL;
ListNode* nextTemp = NULL;
ListNode* curr = head;
while (curr != NULL) {
nextTemp = curr->next;//保持下一个节点
curr->next = prev;//指向前序节点
prev = curr; //前序节点移动
curr = nextTemp; //当前节点移动
}
return prev;
}
};
5)是否存在环(L141)
- 快慢指针,符合返回
题目:给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
int hasCycle(struct Node *head) {
if (head == NULL) return 0;
// p 是慢指针,q 是快指针
struct Node *p = head, *q = head;
// 每次循环,p 走1步,q 走2步
do {
p = p->next;
q = q->next;
if (q == NULL) return 0;
q = q->next;
} while (p != q && q);
return p == q;
}
6) 环形链表 II(142)
class Solution {
public:
ListNode* detectCycle(ListNode *head) {
ListNode *fast = head, *slow = head;
while (true) {//找到环
if (fast == NULL || fast->next == NULL) return NULL;
fast = fast->next->next;
slow = slow->next;
if (fast == slow) break;
}
fast = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return fast;
}
};
7)合并两个有序链表(L21)
- 值比较,递归左右链表next节点
题目:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
//如果head1 和 head2有一个为空 则直接返回另一个
if (!list1) {
return list2;
}
if (!list2) {
return list1;
}
//递归可以理解为之后的情况都处理好了 只需要解决好当前这步就行了
if (list1->val < list2->val) {
list1->next = mergeTwoLists(list1->next, list2);
return list1;
}
else {
list2->next = mergeTwoLists(list1, list2->next);
return list2;
}
}
};
8)两数相加(L2)
- 使用预先指针的目的在于链表初始化时无可用节点值,而且链表构造过程需要指针移动,进而会导致头指针丢失,无法返回结果;创建节点
题目:给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* pre = new ListNode(0);
ListNode* cur = pre;
int carry = 0;
while(l1 != NULL || l2 != NULL) {
int x = l1 == NULL ? 0 : l1->val;
int y = l2 == NULL ? 0 : l2->val;
int sum = x + y + carry;
carry = sum / 10;
sum = sum % 10;
cur->next = new ListNode(sum);
cur = cur->next;
if(l1 != NULL)
l1 = l1->next;
if(l2 != NULL)
l2 = l2->next;
}
if(carry == 1) {
cur->next = new ListNode(carry);
}
return pre->next;
}
};
9)合并K个升序链表(L23)
- 转换成vector,创建链表!
题目:给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
int len = lists.size();
vector<int> vec;
for(int i = 0;i<len;i++){
while(lists[i] != NULL){
vec.push_back(lists[i]->val);
lists[i] = lists[i]->next;
}
}
sort(vec.begin(),vec.end());
ListNode* dummy = new ListNode(0);
ListNode* pre = new ListNode(0);
ListNode* cur = NULL;
len = vec.size();
for(int i = 0;i<len;i++){
cout << vec[i] << endl;
cur = new ListNode(vec[i]);
if(i == 0)
dummy->next = cur;
pre->next = cur;
pre = cur;
}
return dummy->next;
}
};
3、队列
1) 最近的请求次数(L933)
- 不符合条件弹出队列,新值压入队列
题目:写一个 RecentCounter 类来计算特定时间范围内最近的请求。
请你实现 RecentCounter 类:
RecentCounter() 初始化计数器,请求数为 0 。
int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。
class RecentCounter {
public:
RecentCounter() {
}
int ping(int t) {
while (gQue.size()) {
int temp = gQue.front();
if (temp < t - 3000) gQue.pop();
else break;
}
gQue.push(t);
return gQue.size();
}
private:
queue<int> gQue;
};
4、栈
1)有效的括号(L20)
- 栈里存入左括号,top/pop检查有括号
题目:给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
class Solution {
public:
bool isValid(string s) {
stack<char> st;
char t;
for (char &c: s) {
if (c == '(' || c == '[' || c == '{') st.push(c);
else {
if (st.empty()) return false;
t = st.top(); st.pop();
if (c == ')' && t != '(') return false;
if (c == ']' && t != '[') return false;
if (c == '}' && t != '{') return false;
}
}
return st.empty();
}
};
2) 下一个更大元素 I(L496)
题目:
nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。
返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。
LeetCode编译通过,但是执行超时了!两个栈解法不好
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
stack<int> st;
for (int i = nums2.size() - 1; i >= 0; --i) {
st.push(nums2[i]);
}
vector<int> res(nums1.size());
stack<int> tmpst;
for (int i = nums1.size() - 1; i >= 0; --i) {
bool isFound = false;
int max = -1;
while(!st.empty() && !isFound){
int top = st.top();
if(top > nums1[i]){
max = top;
}
else if (top == nums1[i]){
isFound = true;
}
tmpst.push(top);
}
res.push_back(max);
}
while(!tmpst.empty()){
st.push(tmpst.top());
}
return res;
}
};
6、HashTable
1) 是否存在重复元素(L217)
- 存入hash,检查值是否存在
题目:给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
if(nums.size() <= 1)
return false;
unordered_map<int,int> map;
for(int num : nums) {
map[num]++;
if(map[num] >= 2)
return true;
}
return false;
}
};
2) 找不同(L389)
- 存入hash,检查值是否存在
题目:给定两个字符串 s 和 t ,它们只包含小写字母。
字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。
请找出在 t 中被添加的字母。
class Solution {
public:
char findTheDifference(string s, string t) {
//哈希表用int类型就可以,字符会自动被转换为asci码的数字
unordered_map<int,int>a;
//把s中所有出现的字符和次数存入哈希表
for(int i=0;i<s.size();i++){
a[s[i]]++;
}
//遍历t
for(int i=0;i<t.size();i++){
//如果出现和哈希表中一样的字符并且剩余次数大于0,就讲次数-1
if(a.find(t[i])!=a.end()&&a[t[i]]>0)
a[t[i]]--;
//否则无论是没有出现的字符,或是次数用尽的字符都会被返回
else return t[i];
}
return ' ';
}
};
3) 下一个更大元素 I(L496)
- 哈希+栈,栈里不为空有个更大元素
题目:
nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。
返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。
哈希表解法不会超时
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int,int> hashmap;
stack<int> st;
for (int i = nums2.size() - 1; i >= 0; --i) {
int num = nums2[i];
while (!st.empty() && num >= st.top()) {
st.pop();
}
hashmap[num] = st.empty() ? -1 : st.top();
st.push(num);
}
vector<int> res(nums1.size());
for (int i = 0; i < nums1.size(); ++i) {
res[i] = hashmap[nums1[i]];
}
return res;
}
};
4)每日温度(L739)
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n = temperatures.size();
vector<int> res(n);
stack<int> st;
for (int i = 0; i < temperatures.size(); ++i) {
while (!st.empty() && temperatures[i] > temperatures[st.top()]) {
int t = st.top(); st.pop();
res[t] = i - t;
}
st.push(i);
}
return res;
}
};
6、HashSet
1)设计哈希集合(L705)
- 哈希再处理
题目:不使用任何内建的哈希表库设计一个哈希集合(HashSet)。
实现 MyHashSet 类:
void add(key) 向哈希集合中插入值 key 。
bool contains(key) 返回哈希集合中是否存在这个值 key 。
void remove(key) 将给定值 key 从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。
class MyHashSet {
private:
vector<list<int>> data;
static const int base = 769;
static int hash(int key) {
return key % base;
}
public:
/** Initialize your data structure here. */
MyHashSet(): data(base) {}
void add(int key) {
int h = hash(key);
for (auto it = data[h].begin(); it != data[h].end(); it++) {
if ((*it) == key) {
return;
}
}
data[h].push_back(key);
}
void remove(int key) {
int h = hash(key);
for (auto it = data[h].begin(); it != data[h].end(); it++) {
if ((*it) == key) {
data[h].erase(it);
return;
}
}
}
/** Returns true if this set contains the specified element */
bool contains(int key) {
int h = hash(key);
for (auto it = data[h].begin(); it != data[h].end(); it++) {
if ((*it) == key) {
return true;
}
}
return false;
}
};
7、二叉树
前中后序遍历:L144,L94,L145
144、二叉树前序遍历
- 非递归:push,不为空,top/pop/push_back,value,right
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
TreeNode *Curr = root;
std::vector<int> result;
reorderTraversal(Curr,result);
return result;
}
void reorderTraversal(TreeNode* Curr,vector<int>& result) {
if(Curr == NULL)
return;
result.push_back(Curr->val);
reorderTraversal(Curr->left,result);
reorderTraversal(Curr->right,result);
}
};
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> stackNode;
vector<int> result;
if (root == NULL) return result;
stackNode.push(root);
while (!stackNode.empty()) {
TreeNode* node = stackNode.top();
stackNode.pop();
result.push_back(node->val);
if (node->right)
stackNode.push(node->right);
if (node->left)
stackNode.push(node->left);
}
return result;
}
};
94、二叉树中序遍历
- 非递归:push,left,top/pop/push_back,value,right
题目:给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
递归实现:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
TreeNode *Curr = root;
std::vector<int> result;
reorderTraversal(Curr,result);
return result;
}
void reorderTraversal(TreeNode* Curr,vector<int>& result) {
if(Curr == NULL)
return;
reorderTraversal(Curr->left,result);
result.push_back(Curr->val);
reorderTraversal(Curr->right,result);
}
};
迭代实现:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> stk;
while (root != nullptr || !stk.empty()) {
while (root != nullptr) {
stk.push(root);
root = root->left;
}
root = stk.top();
stk.pop();
res.push_back(root->val);
root = root->right;
}
return res;
}
};
145、二叉树后序遍历
- 非递归:push,不为空,top/pop/push_back,左右+反转
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
TreeNode *Curr = root;
std::vector<int> result;
reorderTraversal(Curr,result);
return result;
}
void reorderTraversal(TreeNode* Curr,vector<int>& result) {
if(Curr == NULL)
return;
reorderTraversal(Curr->left,result);
reorderTraversal(Curr->right,result);
result.push_back(Curr->val);
}
};
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> stackNode;
vector<int> result;
if (root == NULL) return result;
stackNode.push(root);
while (!stackNode.empty()) {
TreeNode* node = stackNode.top();
stackNode.pop();
result.push_back(node->val);
if (node->left) stackNode.push(node->left);
if (node->right) stackNode.push(node->right);
}
reverse(result.begin(), result.end()); // 将结果反转
return result;
}
};
102、二叉树层序遍历
- *队列实现:front/pop/push_back,左右入队
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector <vector <int>> ret;
if (!root) {
return ret;
}
queue <TreeNode*> q;
q.push(root);
while (!q.empty()) {
int currentLevelSize = q.size();
vector<int> vec;
for (int i = 1; i <= currentLevelSize; ++i) {
TreeNode* node = q.front(); q.pop();
vec.push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
ret.push_back(vec);
}
return ret;
}
};
114、二叉树展开为链表
- 前序遍历后再展开
题目:给你二叉树的根结点 root ,请你将它展开为一个单链表:
展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历顺序相同。
class Solution {
public:
void reorderTraversal(TreeNode* Curr,vector<TreeNode*>& result) {
if(Curr == NULL)
return;
result.push_back(Curr);
reorderTraversal(Curr->left,result);
reorderTraversal(Curr->right,result);
}
void flatten(TreeNode* root) {
TreeNode *Curr = root;
std::vector<TreeNode*> result;
reorderTraversal(Curr,result);
int len = result.size();
for(int i = 1;i< len;i++){
TreeNode *prev = result[i - 1], *curr = result[i];
prev->left = NULL;
prev->right = curr;
}
}
};
105、从前序与中序遍历序列构造二叉树
- 前序和中序遍历关于根节点和左右子树分治算法
题目:给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return pre_order(0, preorder.size() - 1, 0, inorder.size() - 1, preorder, inorder);
}
TreeNode *pre_order(int leftpre, int rightpre, int leftin, int rightin, vector<int> &pre, vector<int> &in) {
if (leftpre > rightpre || leftin > rightin)
return NULL;
TreeNode *root = new TreeNode(pre[leftpre]);//头结点
//中序遍历根节点位置,分左右子树
int rootin = leftin;
while (rootin <= rightin && in[rootin] != pre[leftpre])
rootin++;
int left = rootin - leftin;
root->left = pre_order(leftpre + 1, leftpre + left, leftin, rootin - 1, pre, in);
root->right = pre_order(leftpre + left + 1, rightpre, rootin + 1, rightin, pre, in);
return root;
}
};
236、二叉树的最近公共祖先
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == NULL)
return NULL;
if(root == p || root ==q)
return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if(left == NULL)
return right;
if(right == NULL)
return left;
if(left && right)
return root;
return NULL;
}
};
8、堆
1)数组中第K个最大元素(L215)
2)前K个高频单词(L692)
二、算法
1、双指针
1)环形链表(L141)
2)救生艇(L881)
- 至少一人走,多带一个+1
题目:给定数组 people 。people[i]表示第 i 个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit。
每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。
返回 承载所有人所需的最小船数 。
class Solution {
public:
int numRescueBoats(vector<int>& people, int limit) {
if(people.size() == 0)
return 0;
std::sort(people.begin(),people.end());
int i = 0;
int j = people.size() - 1;
int result = 0;
while(i <= j){
if((people[i] + people[j]) <= limit)
i = i + 1;//符合了才带走
j = j - 1;//每次必带走
result = result + 1;
}
return result;
}
3)盛最多水的容器(L11)
- 双指针,正确性证明:移动短板!
class Solution {
public:
int maxArea(vector<int>& height) {
int i = 0, j = height.size() - 1, res = 0;
while(i < j) {
res = height[i] < height[j] ?
max(res, (j - i) * height[i++]):
max(res, (j - i) * height[j--]);
}
return res;
}
};
- 双指针,暴力法超时!
class Solution {
public:
int maxArea(vector<int>& height) {
int n = height.size();
int area = 0;
int maxArea = 0;
for(int i = 0 ;i < n ;i ++){
for(int j = n-1 ;j > i ;j--){
cout << height[i] << " " << height[j] << endl;
area = min(height[i],height[j])*(j-i);
if(area > maxArea)
maxArea = area;
}
}
return maxArea;
}
};
4)三数之和(L11)
- 排序+双指针,连续相同元素跳过,可能产生相同结果!
题目:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<int> vecThreeSum;
vector<vector<int>> vecSums;
int len = nums.size();
if(len < 3)
return vecSums;
sort(nums.begin(),nums.end());
if(nums[0] > 0 || nums[len-1] < 0)
return vecSums;
for(int i = 0 ;i < len-1 ; i++){
if(nums[i] > 0)
return vecSums;
if(i > 0 && nums[i] == nums[i-1])
continue;//避免重复元素
int L = i + 1;
int R = len - 1;
while(L<R){
vecThreeSum.clear();
if(nums[i] + nums[L] + nums[R] == 0){
vecThreeSum.push_back(nums[i]);
vecThreeSum.push_back(nums[L]);
vecThreeSum.push_back(nums[R]);
vecSums.push_back(vecThreeSum);
while(L<R and nums[L]==nums[L+1])
L=L+1;
while(L<R and nums[R]==nums[R-1])
R=R-1;
L=L+1;
R=R-1;
}
else if(nums[i]+nums[L]+nums[R]>0)
R=R-1;
else
L=L+1;
}
}
return vecSums;
}
};
2、二分查找
1)二分查找(L704)
- *l+(r-l)/2
题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.size() == 0)
return -1;
int l = 0;
int r = nums.size() -1;
while(l<=r){
int m = l + (r-l)/2;
if(nums[m] == target)
return m;
else if(nums[m] < target)
l = m + 1;
else
r = m - 1;
}
return -1;
}
};
2)搜索插入位置(L35)
题目:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
与704区别是return left指针
3)寻找区间峰值(L162)
- 左右特例,双指针逼近
题目:
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞ 。
你必须实现时间复杂度为 O(log n) 的算法来解决此问题。
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int n = nums.size();
if(n == 1) return 0;
// 先特判两边情况
if(nums[0] > nums[1]) return 0;
if(nums[n - 1] > nums[n - 2]) return n - 1;
int l = 0, r = n - 1;
while(l <= r) {
int mid = (l + r) / 2;// 当前为峰值
if(mid >= 1 && nums[mid] > nums[mid - 1] && nums[mid] > nums[mid + 1]) {
return mid;
} else if(mid >= 1 && nums[mid] < nums[mid - 1]) {// 峰值在 mid 左侧
r = mid - 1;
} else if(nums[mid] < nums[mid + 1]) {// 峰值在 mid 右侧
l = mid + 1;
}
}
return -1;
}
};
4)搜索二维矩阵(L74)
- 除余下标,双指针逼近
题目:编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int col = matrix.size();
int row = matrix[0].size();
int left = 0;
int right = col*row -1;
while(left <= right){
int mid = (left + right)/2;
if(matrix[mid/row][mid%row] == target)
return true;
else if (matrix[mid/row][mid%row] > target)
right = mid -1;
else
left = mid + 1;
}
return false;
}
};
5) 搜索旋转排序数组(L33)
class Solution {
public:
int search(vector<int>& nums, int target) {
int len = nums.size();
int low = 0,high = len -1,mid = 0;
while(low<=high){
mid = (low+high)/2;
if(nums[mid] == target)
return mid;
if(nums[0] <= nums[mid]){
if(nums[0] <= target && target < nums[mid])
high = mid - 1;
else
low = mid + 1;
}
else{
if(nums[mid] < target && target <= nums[len-1])
low = mid + 1;
else
high = mid - 1;
}
}
return -1;
}
};
3、滑动窗口
1)长度最小的子数组(L209)
- 凑足数+滑动窗口
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int length = nums.size();
if(length == 0){
return 0;
}
int result = length + 1;
int total = 0;
int i = 0;
int j = 0;
while(j < length){
total = total + nums[j];
j = j + 1;
while(total>=target){
if(result > (j-i)){
result = j-i;
}
total = total - nums[i];
i= i +1;
}
}
if(result == (length + 1)){
return 0;
}
return result;
}
};
2)定长子串中元音最大数目(L1456)
- 凑足k+滑动窗口,比较结果
给你字符串 s 和整数 k 。
请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。
英文中的 元音字母 为(a, e, i, o, u)。
class Solution {
public:
int maxVowels(string s, int k) {
int result = 0;
int count = 0;
if(s.length() == 0){
return 0;
}
for(int i = 0;i < k ;i++){
if(s[i] == 'a' || s[i] == 'e' ||s[i] == 'i' ||s[i] == 'o' ||s[i] == 'u')
count = count + 1;
result = count;
}
for(int i = k;i < s.length() ;i++){
if(s[i] == 'a' || s[i] == 'e' ||s[i] == 'i' ||s[i] == 'o' ||s[i] == 'u')
count = count + 1;
if(s[i-k] == 'a' || s[i-k] == 'e' ||s[i-k] == 'i' ||s[i-k] == 'o' ||s[i-k] == 'u')
count = count - 1;
if(result < count)
result = count;
}
return result;
}
};
3)无重复字符的最长子串(L3)
- 典型滑动
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if(s.size() == 0) return 0;
unordered_set<char> lookup;
int maxStr = 0;
int left = 0;
for(int i = 0; i < s.size(); i++){
while (lookup.find(s[i]) != lookup.end()){
lookup.erase(s[left]);
left ++;
}
maxStr = max(maxStr,i-left+1);
lookup.insert(s[i]);
}
return maxStr;
}
};
4、递归
1)斐波拉契数列(L509)
题目:斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1。给定 n ,请计算 F(n) 。
2)反转链表(L206)
- 倒数第二个节点参考,next->next指回自己,next为null
题目:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == NULL || head->next == NULL)
return head;
ListNode* p = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return p;
}
};
3)反转字符串(L344)
- 终止条件+递归+交换
题目:编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
class Solution {
public:
void reverseString(vector<char>& s) {
if(s.size() == 0 || s.size() == 1)
return;
int left = 0;
int right = s.size() - 1;
recursion(s,left,right);
}
void recursion(vector<char>& s,int left,int right){
if(left>=right)
return;
swap(s[left],s[right]);
recursion(s,left+1,right-1);
}
};
5、分治法
1)多数元素(L169)
- 左右递归+比较计算左右最大值
题目:给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
class Solution {
public:
int majorityElement(vector<int>& nums) {
return getMajority(nums,0,(nums.size()-1));
}
int getMajority(vector<int>& nums,int left,int right) {
if(left == right)
return nums[left];
int mid = left + (right-left)/2;
int leftMajority=getMajority(nums,left,mid);
int rightMajority=getMajority(nums,mid+1,right);
//计算多数元素
if(leftMajority==rightMajority)
return leftMajority;
int leftcount=0;
int rightcount=0;
for(int i=left;i<=right;i++){
if(nums[i] == leftMajority)
leftcount++;
else if(nums[i] == rightMajority)
rightcount++;
}
if(leftcount > rightcount)
return leftMajority;
else
return rightMajority;
}
};
2)最大子数组和(L53)
分值算法实现太复杂,后期看下动态规划实现
6、回溯法
1)括号生成(L22)
- 回溯终止条件+回溯调用&弹出
题目:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
std::string str = "";
backtracking(n,result,0,0,str);
return result;
}
void backtracking(int n,vector<string>& result,int left,int right,std::string & str) {
if(right > left)
return;
if(left == right && left == n){
result.push_back(str);
return;
}
if(left < n){
str = str + '(';
backtracking(n,result,left+1,right,str);
str.pop_back();
}
if(right < n){
str = str + ')';
backtracking(n,result,left,right+1,str);
str.pop_back();
}
}
};
2)子集(L78)
3)电话号码的字母组合(L17)
- map初始化(字符串)+回溯回溯调用&弹出
class Solution {
public:
vector<string> letterCombinations(string digits) {
vector<string> combinations;
if (digits.empty()) {
return combinations;
}
unordered_map<char, string> phoneMap{
{'2', "abc"},
{'3', "def"},
{'4', "ghi"},
{'5', "jkl"},
{'6', "mno"},
{'7', "pqrs"},
{'8', "tuv"},
{'9', "wxyz"}
};
string combination;
backtrack(combinations, phoneMap, digits, 0, combination);
return combinations;
}
void backtrack(vector<string>& combinations, const unordered_map<char, string>& phoneMap, const string& digits, int index, string& combination) {
if (index == digits.length()) {
combinations.push_back(combination);
} else {
char digit = digits[index];
const string& letters = phoneMap.at(digit);
for (const char& letter: letters) {
combination.push_back(letter);
backtrack(combinations, phoneMap, digits, index + 1, combination);
combination.pop_back();
}
}
}
};
5)组合总和(L39)
- 回溯+DFS
题目:给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
class Solution {
public:
vector<int> path;
vector<vector<int>> vec;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtraveral(candidates,target,0);
return vec;
}
private:
void backtraveral(vector<int>& candidates, int target,int start){
//终止条件
if(target < 0)
return;
if(target == 0){
vec.push_back(path);
return;
}
for (int i = start; i < candidates.size(); ++i){
//> 开始回溯
path.push_back(candidates[i]);
backtraveral(candidates, target-candidates[i], i);
path.pop_back();
}
}
};
6)全排列(L46)
- 回溯+交换维护已填充子数组+恢复
题目:给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 。你可以 按任意顺序 返回答案。
class Solution {
public:
vector<vector<int>> vec;
vector<vector<int>> permute(vector<int>& nums) {
vector<int> path(nums);
backtraveral(nums,path,0);
return vec;
}
void backtraveral(vector<int>& nums,vector<int>& path, int first){
if(first > nums.size())
return;
if(first == nums.size()){
vec.push_back(path);
return;
}
for(int i = first;i < nums.size();i++){
swap(path[i], path[first]);
backtraveral(nums,path,first + 1);
swap(path[i], path[first]);
}
return;
}
};
7)子集(L78)
- 回溯+去重复+恢复
题目:给你一个整数数组 nums ,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
class Solution {
public:
vector<int> path;
vector<vector<int>> vec;
vector<vector<int>> subsets(vector<int>& nums) {
backtraking(nums,0);
return vec;
}
void backtraking(vector<int>& nums, int start){
if(start > nums.size())
return;
if(start == nums.size()){
vec.push_back(path);
return;
}
vec.push_back(path);
for(int i = start;i < nums.size();i++){
path.push_back(nums[i]);
backtraking(nums,i + 1);
path.pop_back();
}
return;
}
};
7、DFS
1)二叉搜索树的范围和(L938)
2)岛屿数量(L200)
8、BFS
1)二叉树层序遍历(L102)
2)二叉树层序遍历II(L107)
9、并查集Union Find
1)岛屿数量(L200)
2)朋友圈(L547)
10、贪心算法
1)零钱兑换(L322)
- 1 + dp[i - coin]
一直不理解这个状态方程,知道看到了labuladong这张图
LeetCode大神零钱兑换已理解版本
// 数组大小为 amount + 1,初始值也为 amount + 1
vector<int> dp(amount + 1, amount + 1);
// base case
dp[0] = 0;
// 外层 for 循环在遍历所有状态的所有取值
for (int i = 0; i < dp.size(); i++) {
// 内层 for 循环在求所有选择的最小值
for (int coin : coins) {
// 子问题无解,跳过
if (i - coin < 0) continue;
dp[i] = min(dp[i], 1 + dp[i - coin]);
}
}
return (dp[amount] == amount + 1) ? -1 : dp[amount];
2)玩筹码(L1217)
3)跳跃游戏(L55)
4)优势洗牌
- ***贪心,类似于田忌赛马,最大值小就给最小的 ***
给定两个大小相等的数组 nums1 和 nums2,nums1 相对于 nums 的优势可以用满足 nums1[i] > nums2[i] 的索引 i 的数目来描述。
返回 nums1 的任意排列,使其相对于 nums2 的优势最大化。
示例 1:
输入:nums1 = [2,7,11,15], nums2 = [1,10,4,11]
输出:[2,11,7,15]
class Solution {
public:
vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(),nums1.end());
vector<pair<int,int>> vt;
for(int i=0;i<nums2.size();i++){
vt.push_back(make_pair(nums2[i],i));
}
sort(vt.begin(),vt.end());
vector<int> ans(nums2.size());
int l1=0,r1=nums1.size()-1,r2=nums2.size()-1;
while(r2>=0){
if(nums1[r1]>vt[r2].first){
ans[vt[r2].second]=nums1[r1];
r1--;
}
else{
ans[vt[r2].second]=nums1[l1];
l1++;
}
r2--;
}
return ans;
}
};
11、动态规划
1)斐波那契数列(L509)
题目:斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1。给定 n ,请计算 F(n) 。
class Solution {
public:
int fib(int n) {
int dp[31];
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i <= n; ++ i) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
};
2)不同路径(L62)
- ***边初始化成1,dp数组 ***
题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> f(m, vector<int>(n));
for (int i = 0; i < m; ++i) {
f[i][0] = 1;
}
for (int j = 0; j < n; ++j) {
f[0][j] = 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];
}
};
3)买股票的最佳时机(L121)
题目:给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
暴力解法:超时了。。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int total = 0;
for(int i=0;i<prices.size()-1;i++){
for(int j=i+1;j<prices.size();j++){
int tmp = prices[j] - prices[i];
if((tmp > total) && (tmp > 0))
total = tmp;
}
}
return total;
}
};
滚动窗口:
- ***maxprofit, minprice ***
class Solution {
public:
int maxProfit(vector<int>& prices) {
int inf = 1e9;
int minprice = inf, maxprofit = 0;
for (int price: prices) {
maxprofit = max(maxprofit, price - minprice);
minprice = min(price, minprice);
}
return maxprofit;
}
};
动态规划:
- ***dp[i],dp_min[i],result ***
class Solution{
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if(len < 2){
return 0;
}
vector<int> dp(len+1);
vector<int> dp_min(len+1);
dp_min[0] = prices[0];
int result = 0;
for (int i = 1;i <len ;i++) {
dp[i] = prices[i]-dp_min[i-1];
dp_min[i] = min(prices[i], dp_min[i-1]);
result = max(result,dp[i]);
}
return result;
}
};
4)爬楼梯(L70)
- 初始值+dp数组
题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
class Solution {
public:
/* 动态规划五部曲:
* 1.确定dp[i]的下标以及dp值的含义: 爬到第i层楼梯,有dp[i]种方法;
* 2.确定动态规划的递推公式:dp[i] = dp[i-1] + dp[i-2];
* 3.dp数组的初始化:因为提示中,1<=n<=45 所以初始化值,dp[1] = 1, dp[2] = 2;
* 4.确定遍历顺序:分析递推公式可知当前值依赖前两个值来确定,所以递推顺序应该是从前往后;
* 5.打印dp数组看自己写的对不对;
*/
int climbStairs(int n) {
if (n <= 1) return n;
/* 定义dp数组 */
vector<int> dp(n+1);
/* 初始化dp数组 */
dp[1] = 1;
dp[2] = 2;
/* 从前往后遍历 */
for(int i = 3; i <= n; i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
};
4)最小路径和(L64)
- dp数组+最小路径
题目:给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = m > 0? grid[0].size():0;
if(m == 0)
return 0;
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(i == 0 && j == 0) continue;
else if(i == 0) grid[i][j] = grid[i][j - 1] + grid[i][j];
else if(j == 0) grid[i][j] = grid[i - 1][j] + grid[i][j];
else grid[i][j] = min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j];
}
}
return grid[m - 1][n - 1];
}
};
6)完全平方数(L279)
7)最大正方形(L221)
12、记忆化搜索
1)斐波那契数列(L509)
2)零钱兑换(L322)
13、前缀树Trie
class Trie {
private:
vector<Trie*> children;
bool isEnd;
Trie* searchPrefix(string prefix) {
Trie* node = this;
for (char ch : prefix) {
ch -= 'a';
if (node->children[ch] == nullptr) {
return nullptr;
}
node = node->children[ch];
}
return node;
}
public:
Trie() : children(26), isEnd(false) {}
void insert(string word) {
Trie* node = this;
for (char ch : word) {
ch -= 'a';
if (node->children[ch] == nullptr) {
node->children[ch] = new Trie();
}
node = node->children[ch];
}
node->isEnd = true;
}
bool search(string word) {
Trie* node = this->searchPrefix(word);
return node != nullptr && node->isEnd;
}
bool startsWith(string prefix) {
return this->searchPrefix(prefix) != nullptr;
}
};
1)实现Trie(L208)
2)词典中最长的单词(L720)
3)前K个高频单词(L692)
LeetCode Top 100
1、两数之和
- mapReverseKeyNums[nums[i]] = i;差值存在?和i
题目:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
暴力法:双指针
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> result;
if(nums.size() < 2){
return result;
}
for(int i = 0;i < nums.size() -1;i++){
for(int j=i+1;j<nums.size();j++){
if(nums[i] + nums[j] == target){
result.push_back(i);
result.push_back(j);
return result;
}
}
}
return result;
}
};
使用Map,增加查询效率
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int, int> mapReverseKeyNums;
for (int i = 0; i < nums.size(); i++) {
if (mapReverseKeyNums.find(target - nums[i]) != mapReverseKeyNums.end()) {
return {mapReverseKeyNums[target - nums[i]], i};
}
mapReverseKeyNums[nums[i]] = i;
}
return {};
}
};
53、最大子数组和
- dp数组取之前和+当前值,与当前值最大值;result和dp数组最大值
题目:给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
LeetCode题解使用动态规划讲的比较清楚
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
//类似寻找最大最小值的题目,初始值一定要定义成理论上的最小最大值
int max = INT_MIN;
int numsSize = int(nums.size());
for (int i = 0; i < numsSize; i++){
int sum = 0;
for (int j = i; j < numsSize; j++){
sum += nums[j];
if (sum > max){
max = sum;
}
}
}
return max;
}
};
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
//类似寻找最大最小值的题目,初始值一定要定义成理论上的最小最大值
int result = INT_MIN;
int numsSize = int(nums.size());
//dp[i]表示nums中以nums[i]结尾的最大子序和
vector<int> dp(numsSize);
dp[0] = nums[0];
result = dp[0];
for (int i = 1; i < numsSize; i++) {
dp[i] = max(dp[i - 1] + nums[i], nums[i]);
result = max(result, dp[i]);
}
return result;
}
};
338、比特位计数
题目:给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。
class Solution {
public:
int countOnes(int x) {
int ones = 0;
while (x > 0) {
x &= (x - 1);
ones++;
}
return ones;
}
vector<int> countBits(int n) {
vector<int> bits(n + 1);
for (int i = 0; i <= n; i++) {
bits[i] = countOnes(i);
}
return bits;
}
};
5、最长回文子串
题目:给你一个字符串 s,找到 s 中最长的回文子串。
暴力法:超时了,从大到小找到最大回文子串
class Solution {
public:
string longestPalindrome(string s) {
int len = s.length();
string ans = "";
for(int i = len;i > 0;i--){
for(int j = 0;j< len - i +1;j++){
//cout << s.substr(j,i) << endl;
if(isPalindromic(s.substr(j,i))){
ans = s.substr(j,i);
return ans;
}
}
}
return ans;
}
bool isPalindromic(string s) {
int len1 = s.length();
for (int i = 0; i < len1 / 2; i++) {
if (s[i] != s[len1 - i - 1]) {
return false;
}
}
return true;
}
};
中心拓展法:
- 两个点分别拓展后截取
class Solution {
public:
string longestPalindrome(string s) {
if (s.length() < 1) return "";
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int len = max(len1, len2);
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substr(start, (end - start + 1));
}
int expandAroundCenter(string s, int left, int right) {
int L = left, R = right;
while (L >= 0 && R < s.length() && s[L] == s[R]) {
L--;
R++;
}
return R - L - 1;
}
};
543、二叉树的直径
- 递归深度过程中找最大值
题目:给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
class Solution {
int maxd=0;
public:
int diameterOfBinaryTree(TreeNode* root) {
depth(root);
return maxd;
}
int depth(TreeNode* node){
if(node==NULL){
return 0;
}
int Left = depth(node->left);
int Right = depth(node->right);
maxd=max(Left+Right,maxd);//将每个节点最大直径(左子树深度+右子树深度)当前最大值比较并取大者
return max(Left,Right)+1;//返回节点深度
}
};
461、汉明距离
- 异或&移位
题目:两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
给你两个整数 x 和 y,计算并返回它们之间的汉明距离。
class Solution {
public:
int hammingDistance(int x, int y) {
long result = 0;
int s = x^y;
while(s){
result +=s & 1;
s >>= 1;
}
return result;
}
};
448、找到所有数组中消失的数字
- 加入hash表,遍历检查
题目:给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
int len = nums.size();
std::map<int,int> mapCount;
for(int i = 0;i<len;i++){
if(mapCount.find(nums[i])!=mapCount.end())
mapCount[nums[i]] = mapCount[nums[i]] + 1;
else
mapCount[nums[i]] = 1;
}
vector<int> vecLost;
for(int i = 1;i<=len;i++){
if(mapCount.find(i)==mapCount.end()){
vecLost.push_back(i);
}
}
return vecLost;
}
};
101、对称二叉树
- 值+左/右节点递归检查
题目:给你一个二叉树的根节点 root , 检查它是否轴对称。
class Solution {
public:
bool check(TreeNode *p, TreeNode *q) {
if (!p && !q) return 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);
}
};
104、二叉树的最大深度
- 递归,深度+1
题目:给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
class Solution {
public:
int maxDepth(TreeNode* root){
if(root==NULL){
return 0;
}
int Left = maxDepth(root->left);
int Right = maxDepth(root->right);
return max(Left,Right) + 1;//返回节点深度
}
};
226、翻转二叉树
- 逐层翻转并递归
题目:给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root == NULL){
return NULL;
}
TreeNode* tmpNode = root->left;
root->left = root->right;
root->right = tmpNode;
invertTree(root->left);
invertTree(root->right);
return root;
}
};
234、回文链表
- 转换成对数组比较
题目:给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
class Solution {
public:
bool isPalindrome(ListNode* head) {
std::vector<int> vecNum;
ListNode* curr = head;
while(curr!= NULL){
vecNum.push_back(curr->val);
curr = curr->next;
}
if(vecNum.size() ==1){
return true;
}
for(int i = 0,j = vecNum.size() -1;i<vecNum.size(),i<j;i++,j--){
if(vecNum[i] != vecNum[j])
return false;
}
return true;
}
};
160、相交链表
- 插入map,两个head遍历
题目:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_set<ListNode *> visited;
ListNode *temp = headA;
while (temp != nullptr) {
visited.insert(temp);
temp = temp->next;
}
temp = headB;
while (temp != nullptr) {
if (visited.count(temp)) {
return temp;
}
temp = temp->next;
}
return nullptr;
}
};
双指针法:没看懂
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;
}
};
155、最小栈
- 维护两个栈,其中一个栈保存最小值
题目:设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。
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(min(min_stack.top(), x));
}
void pop() {
x_stack.pop();
min_stack.pop();
}
int top() {
return x_stack.top();
}
int getMin() {
return min_stack.top();
}
};
96、不同的二叉搜索树
- 动态规划/DFS/回溯
递归、回溯、深度优先搜索(DFS)
什么是红黑树[程序员景禹]
题目:给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
//动态规划解法
class Solution {
public:
int numTrees(int n) {
vector<int> G(n + 1, 0);
G[0] = 1;
G[1] = 1;
for (int i = 2; i <= n; ++i) {
for (int j = 1; j <= i; ++j) {
G[i] += G[j - 1] * G[i - j];
}
}
return G[n];
}
};
//记忆化搜索+DFS
class Solution {
public:
unordered_map<int, int> cache;
int dfs(int n){
if (n <= 1) return 1;
else if (cache.find(n) == cache.end()){
int c = 0;
for (int i = 0; i < n; ++i)
c += dfs(i) * dfs(n - 1 - i);
cache[n] = c;
return c;
}
else
return cache[n];
}
int numTrees(int n) {
return dfs(n);
}
};
406、根据身高重建队列
class Solution {
public:
// 身高从大到小排(身高相同k小的站前面)
static bool cmp(const vector<int> a, const vector<int> b) {
if (a[0] == b[0]) return a[1] < b[1];
return a[0] > b[0];
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort (people.begin(), people.end(), cmp);
list<vector<int>> que; // list底层是链表实现,插入效率比vector高的多
for (int i = 0; i < people.size(); i++) {
// 插入到下标为position的位置
// [[7,0], [7,1], [6,1], [5,0], [5,2],[4,4]] 取二位数组第二个元素
int position = people[i][1];
//cout << position << endl;
std::list<vector<int>>::iterator it = que.begin();
while (position--) { // 寻找在插入位置
it++;
}
que.insert(it, people[i]);
}
return vector<vector<int>>(que.begin(), que.end());
}
};
287. 寻找重复数
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = 0;
int fast = 0;
slow = nums[slow];
fast = nums[nums[fast]];
while(slow != fast){//找首次相遇点
slow = nums[slow];
fast = nums[nums[fast]];
}
int pre1 = 0;
int pre2 = slow;
while(pre1 != pre2){//找环入口
pre1 = nums[pre1];
pre2 = nums[pre2];
}
return pre1;
}
};
347. 前 K 个高频元素
class Solution {
public:
static bool cmp(const pair<int, int> &a, const pair<int, int> &b){
return a.second > b.second;
}
vector<int> topKFrequent(vector<int>& nums, int k) {
int len = nums.size();
map<int,int> mapNumFreq;
for(int i = 0;i < len ;i++){
if(mapNumFreq.find(nums[i]) == mapNumFreq.end())
mapNumFreq[nums[i]] = 1;
else
mapNumFreq[nums[i]] += 1;
cout << nums[i] << " " << mapNumFreq[nums[i]] << endl;
}
vector<pair<int,int>> vec(mapNumFreq.begin(),mapNumFreq.end());//用map对该容器初始化
sort(vec.begin(),vec.end(),cmp);//用sort排序
vector<int> vecRes;
vector<pair<int,int>>::iterator iter;
for(iter = vec.begin();iter < vec.begin()+k;iter++){
vecRes.push_back(iter->first);
}
return vecRes;
}
};
四、面试过题目
1、回形打印/输出一个回形数(二维数组)
- 4个指针沿边界滚动
#include <iostream>
#include <string>
#include <memory.h>
#include <stdio.h>
using namespace std;
int row,col;
int num[105][105];
int main()
{
cin >> row >> col;
for( int i = 1; i <= row ; i++ )
for( int j = 1; j <= col; j++ )
cin >> num[i][j];
int row_step = 1, col_step = 1;
while( row_step <= row || col_step <= col )
{
for( int i = row_step, j = col_step; j <= col && row_step <= row; j++ )//j <= col限定范围,row_step递增
cout << num[i][j] << endl;
row_step++;
for( int i = row_step, j = col; i <= row && col_step <= col; i++ )//i <= row限定范围,col_step递增
cout << num[i][j] << endl;
col--;
for( int i = row, j = col; j >= col_step && row_step <= row; j-- )
cout << num[i][j] << endl;
row--;
for( int i = row, j = col_step; i >= row_step && col_step <= col; i-- )
cout << num[i][j] << endl;
col_step++;
}
return 0;
}
}