算法核心面试题【饲养员老师投喂+LeetCode Top 100】

文章目录


本文根据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];
        }
    }

2022春招必会的十道高频算法题

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)

    // 数组大小为 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、不同的二叉搜索树

//动态规划解法
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;
}
}
  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值