Skip to content

Latest commit

 

History

History
486 lines (319 loc) · 10.4 KB

算法总结.md

File metadata and controls

486 lines (319 loc) · 10.4 KB

算法总结

1 摩尔投票法

思路

​ 混战极限一换一,不同的两者一旦遇见就同归于尽,最后活下来的值都是相同的,即要求的结果。

实现

int majorityElement(int* nums, int numsSize){
    int res,cnt;
    res = cnt = 0;
    for ( int i=0; i<numsSize; i++ ){
        if ( cnt==0 ){
            res = nums[i];
            cnt++;
        }else{
            if ( res==nums[i] )
                cnt++;
            else
                cnt--;
        }
    }
    return res;
}

2 TOP K - 快排变形

思路

​ 快速排序中有一步很重要的操作是 partition(划分),从数组中随机选取一个枢纽元素 v,然后原地移动数组中的元素,使得比 v 小的元素在 v 的左边,比 v 大的元素在 v 的右边。如图所示:

partition

​ 我们的目的是寻找最小的 k 个数。假设经过一次 partition 操作,枢纽元素位于下标 m,也就是说,左侧的数组有 m 个元素,是原数组中最小的 m 个数。那么:

  • 若 k = m,我们就找到了最小的 k 个数,就是左侧的数组;
  • 若 k<m ,则最小的 k 个数一定都在左侧数组中,我们只需要对左侧数组递归地 partition 即可;
  • 若 k>m,则左侧数组中的 m 个数都属于最小的 k 个数,我们还需要在右侧数组中寻找最小的 k-m 个数,对右侧数组递归地 partition 即可。

实现

void swap (int* a, int* b);
int partition (int* arr, int st, int end);
int* quickSort (int* arr, int k, int st, int end);
int* getLeastNumbers (int* arr, int arrSize, int k, int* returnSize);

int* getLeastNumbers (int* arr, int arrSize, int k, int* returnSize) {
    int* ans = (int*) malloc (sizeof (int) * 10000);
    *returnSize = k;
    if (k == 0) {
        return ans;
    }
    
    return quickSort (arr, k, 0, arrSize-1);
}

int* quickSort (int* arr, int k, int st, int end) {
    if ( st>=end ){
        return arr;
    }
    int m = partition (arr, st, end);
    if ( m==k ) {
        return arr;
    } else if ( m>k ) {
        return quickSort (arr, k, st, m-1);
    } else {
        return quickSort (arr, k, m+1, end);
    }
}

void swap (int* a, int* b) {
    int t = *a;
    *a = *b;
    *b = t;
}

int partition (int* arr, int st, int end) {
    int pivot = arr[st];
    int i, j;
    i = st;
    j = end;
    while (i < j) {
        //从右开始
        while (i<j && arr[j]>=pivot)
            j--;
        while (i<j && arr[i]<=pivot)
            i++;
        swap (&arr[i], &arr[j]);
    }
    swap (&arr[i], &arr[st]);
    return i;
}

3 寻找中位数

思路

​ 维护一个大根堆和一个小根堆,小根堆保存较大的数,大根堆保存较小的数。如此,若总数为奇数,则中位数为小根堆顶元素;若总数为偶数,则中位数为小根堆顶和大根堆顶的平均数。

​ 加入新元素时, 用 **两个堆中元素的个数是否相等 判断 加入哪个堆。**若相等,加入小根堆;若不等,加入大根堆。**加入方式是,先入另一个堆,再把另一个堆的堆顶元素加入该堆。**目的是为了保证,小根堆保存较大的数,大根堆保存较小的数。

实现

class MedianFinder {
public:
    /** initialize your data structure here. */
    priority_queue<int, vector<int>, less<int>> maxHeap;
    priority_queue<int, vector<int>, greater<int>> minHeap;

    MedianFinder() {
    }
    
    void addNum(int num) {
        if ( maxHeap.size()==minHeap.size() ){
            maxHeap.push(num);
            int top = maxHeap.top();
            maxHeap.pop();
            minHeap.push(top);
        }else{
            minHeap.push(num);
            int top = minHeap.top();
            minHeap.pop();
            maxHeap.push(top);
        }
    }
    
    double findMedian() {
        int m = minHeap.size();
        int n = maxHeap.size();
        if ( m==n ){
            return (minHeap.top() + maxHeap.top())*1.0 / 2;
        }else{
            return minHeap.top();
        }
    }
};

4 全排列

思路

​ 将待排列数组,划分为左右两部分,左边为已排列,右边为待排列。first 指向下一个要填的位置。将待填写的数与 first 交换,使 [0,first] 排列完成,继续递归填写下一个数,详细见实现代码。

实现

class Solution {
public:
    void backtrack(vector<vector<int>>& res, vector<int>& output, int first, int len){
        // 所有数都填完了
        if (first == len) {
            res.emplace_back(output);
            return;
        }
        for (int i = first; i < len; ++i) {
            // 动态维护数组
            swap(output[i], output[first]);
            // 继续递归填下一个数
            backtrack(res, output, first + 1, len);
            // 撤销操作
            swap(output[i], output[first]);
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int> > res;
        backtrack(res, nums, 0, (int)nums.size());
        return res;
    }
};

5 组合

思路

​ 回溯。实现 nums 数组中 k 个数的组合排列。

实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void myPrint (vector<int> v, int k);
void backTrace (vector<int> nums, int k, int cur, vector<int> path);

int cnt = 0;

int main () {
    int n, k;
    cin >> n >> k;
    vector<int> nums;
    vector<int> path;
    for ( int i=0; i<n; i++ ) {
        int a;
        cin >> a;
        nums.push_back (a);
    }
    backTrace (nums, k, 0, path);
    cout << cnt << endl;
    return 0;
}

void backTrace (vector<int> nums, int k, int cur, vector<int> path) {
    //cur == next num
    int len = path.size ();
    for ( int i=0; i<len&&len>1; i++ ) {
        if ( abs(path[i]-path[len-1])>d )
            return;
    }
    if ( len==k ) {
        myPrint (path,k);
        return;
    }
    for ( int i=cur; i<nums.size(); i++ ) {
        path.push_back (nums[i]);
        backTrace (nums, k, i+1, path);
        path.pop_back ();
    }
}

void myPrint ( vector<int> v, int k ) {
    for ( int i=0; i<k; i++ )
        cout << v[i] << " ";
    cout << endl;
}

6 动态规划

​ 例题:LeetCode 322.零钱兑换

LeetCode 62.不同路径

LeetCode 55.跳跃游戏

步骤

​ 动态规划分为以下四个步骤:

1 确定状态

  • 最后一步

    即,最优策略的最后一个步骤

  • 转化为子问题

    即规模更小的问题

2 转移方程

​ 根据最后一步转化的子问题,写出转移方程f[x] = ?

3 初始条件和边界情况

​ 确认初始的 f[0] 等。

​ 正确处理数组越界。

4 计算顺序

​ 确定是从前往后计算,还是从后往前计算。确定的标准是,计算 f[x] 时,所用到的值是否已经计算完成。

最大连续子数组的和

思路

dp[i] 为以下标 i 为结尾的连续子数组的最大和。则,dp[i] = max (dp[i-1]+nums[i], nums[i]);。最终所求的即 dp 数组中的最大值。

实现

int maxSubArray(vector<int>& nums) {
    int len = nums.size();
    int dp[len];
    dp[0] = nums[0];
    int maxSub = dp[0];
    for ( int i=1; i<len; i++ ){
        dp[i] = max(dp[i-1]+nums[i], nums[i]);
        if ( maxSub<dp[i] )
            maxSub = dp[i];
    }
    return maxSub;
}

状态压缩


C++ 函数及STL运用

1 堆 / 优先队列

定义

priority_queue<Type, Container, Functional>;

//小根堆
priority_queue<int, vector<int>, greater<int>> q;
//大根堆
priority_queue<int, vector<int>, less<int>> q;

头文件

include <queue>

基本操作

  • top 访问队头元素
  • empty 队列是否为空
  • size 返回队列内元素个数
  • push 插入元素到队尾 (并排序)
  • emplace 原地构造一个元素并插入队列
  • pop 弹出队头元素
  • swap 交换内容

2 排序

  • sort(arr.begin(), arr.end());

3 二维数组 - vector

定义及赋值

vector<vector<int>> array (n);			//n行
cout  << array.size () << endl;
for ( int i=0; i<5; i++ ) {
	array[i].resize (m);				//m列
}
array[2][2] = 99;						//赋值

初始化

vector<vector<int>> vec(row, vector<int> (col,0));

4 ceil ( )

​ 返回大于或等于 x 的最小的整数值

头文件:#include <math>

用法double ceil (double x)

5 to_string ( )

​ 将数值转化为字符串,并返回对应字符串

头文件:#include <string>

用法:str = to_string(3.14);

6 auto 类型

​ C++ 11 新标准

用法

  1. 根据初始化表达式,自动判断被声明变量的类型

    vector<int> vect;
    for ( auto it=vect.begin(); it!=vect.end(); it++ )
        cout << *it << endl;
    //it的类型是 vector<int>::iterator
  2. 用 auto 声明的变量必须初始化

  3. 不能和其他类型组合连用

  4. 函数和模板参数不能被声明为 auto

  5. auto 是一个占位符,而不是一个数据类型,因此不能用于类型转换和其他类似的操作,如 sizeof、typeid

  6. 定义在一个 auto 序列的变量,必须推导成同一类型

7 toupper ( )

​ 将小写字母转换为大写字母

头文件:#include <ctype>

用法:char c = toupper('g');

8 基于范围的 for 循环

用法:

string s ("hello");
for ( auto &c: s )
    c = toupper (c);
cout << s;

**注意:**冒号前的部分,为用来遍历的变量,若要修改容器内的值,需要加上 &;冒号后的部分,为将被遍历的范围。

使用 & 的原因

​ 在范围 for 中需要修改容器中的值,若不通过引用遍历,则遍历的是元素的拷贝,并每次拷贝都需要调用该元素类型的copy constructor。而使用 &,是为了保证原始的元素被使用。

9 sort ( )

​ 默认升序排序

头文件:#include <algorithm>

用法:sort( v.begin(), v.end() );

自定义排序:

sort( str.begin(), str.end(), cmp );
static bool cmp ( const string& a, const string& b ){
    return a+b < b+a;
}

10 INT_MAX

头文件:#include <limits.h>

此头文件还包含 INT_MIN 等各类型的最值