fasd

基本排序算法

冒泡排序

没什么可说的, 改进方法就是加一个标志位防止有序后重复遍历. 由于需要遍历两次, 所以时间复杂度O(N^2)

选择排序

外层从0开始默认outer是最小数的下标, 内存从outer+1位置开始遍历, 不稳定, 如{ 3, 3, 3, 2 }, 当比较最后一个4时, 是第一个3和2交换, 从而不稳定. 内外层遍历两次, 时间复杂度O(N^2)

插入排序

相当于打扑克排序, outer从1到N-1, inner从outer到N-1, 时间复杂度O(N^2)
插入排序选择排序冒泡排序有浪费许多比较的次数
归并排序快的是因为小范围合并为大范围时, 有序可以同过外排方式
小组和为大组时, 组内有序没有浪费, 永远是组与组之间的比较

希尔排序

归并排序

递归把一个数字分隔为两部分, T(N) = 2*T(N/2) + O(N), a= 2, b = 2, N = 1, 时间复杂度O(N*logN), 额外空间复杂度O(N)
递归符合master公式: T(N) = a*T(N/b) + O(N^d)时间复杂度为:
(1) log(b, a) > d --> 复杂度O(N^(log(b, a)))
(2) log(b, a) = d --> 复杂度O(N^d * logN)
(3) log(b, a) < d --> 复杂度O(N^d)

快排

递归公式同归并排序, 由于需要记录分隔点, 所以额外空间复杂度O(logN), 快排做不到稳定性, 因为partition过程做不到稳定

  1. 经典快排与数据状况有关, 这是因为分隔点选取的问题, 如{1, 2, 3, 4, 5, 6}分隔点选取最右边时每次只排序一个数字, 此时时间复杂度为O(N^2)
  2. 如果分隔点选取中位数, 则每次恰好可把数组划分为两部分, 时间复杂度为O(N*logN)
  3. 随机快排的分隔点随机选取, 把复杂度转化为与概率有关, 复杂度长期期望为O(N*logN)

堆排

大根堆结构重要两个函数heapInsert与heapify, 一个上浮, 一个是下沉, 优先级队列就是堆
建立堆的时间复杂度为O(log1) + O(log2) + O(log3) + ... + O(log(N)), 收敛域O(N)

  • 算法: 插入时, 上浮, 直至没有父节点比当前节点大; 排序交换堆顶与堆未元素, 这时堆顶元素下沉, 直至当前节点比子节点都大
  • 传送门 --> 堆排

排序补充

  1. 归并排序可以做到额外空间复杂度O(1), 有难度, 相关搜索"归并排序内部缓存法"
  2. 快排可以做到稳定性, 有难度, 不要求掌握, 相关搜索"01 stable sort"; 快排的优势是partition过程中, 时间复杂度O(N), 空间复杂度O(1)
  3. 有一道题目, 奇数放在数组的左边边, 偶数放在数组右边, 要求原始的相对次数不变, 牛客练习 --> 调整数组顺序使奇数位于偶数前面

排序总结

排序方法 平均情况 最好情况 最坏情况 辅助空间 稳定性
冒泡排序 O(N^2) O(N) O(N^2) O(1) 稳定
选择排序 O(N^2) O(N^2) O(N^2) O(1) 不稳定
插入排序 O(N^2) O(N) O(N^2) O(1) 稳定
希尔排序 O(N*logN) ~ O(N^2) O(N^1.3) O(N^2) O(1) 不稳定
堆排序 O(N*logN) O(N*logN) O(N*logN) O(1) 不稳定
归并排序 O(N*logN) O(N*logN) O(N*logN) O(N) 稳定
快排 O(N*logN) O(N * logN) O(N^2) O(logN) ~ O(N) 不稳定

工程中的综合排序算法

基础类型很长时, 使用快排, 因为基础数据类型不要求稳定
复合数据类型长度很长时, 使用归并排序, 复合数据类型要求稳定
任何数据类型的数组长度很短(<60)时, 使用插入排序

桶排序

桶排序是一个逻辑概念, 具体实现方法是计算排序, 基数排序

  1. 非基于比较的排序, 与被排序的样本的实际数据状况有很大关系, 所以实际中并不经常使用
  2. 时间复杂度O(N), 额外空间复杂度O(N)
  3. 稳定

计算排序

基数排序

一个有序数组A, 另一个无序数组B, 打印B中所有不在A中的数组, A数组长度为N, B数组长度为M

  • 算法1: 暴力法, 时间复杂度O(N^2)
  • 算法2: 遍历B数组, 使用二分法在A中查找相同元素, 时间复杂度O(M * logN)
  • 算法3: 把A数组进行排序, 排序最小时间复杂度O(M*logM), 使用类似外排进行排序, 总时间复杂度O(M*logM) + O(M+N)
    两个游标, p1指向A, p2指向B
    (1) A[p1] < B[p2]; p1++
    (2) A[p1] == B[p2]; p1不移动, 移动p2, 因为B数组中可能有重复数字所以只移动p2
    (3) A[p1] > B[p2]; 打印并移动p2
  • 传送门 --> 时间复杂度理解

使用递归查找数组中的最大值

  • 算法: 二分法查找分隔数字, 返回左右数组中的最大值
    本算法中T(N) = 2*T(N/2) + O(1), a=b=2, d=0, log(2, 2)=1 > d=0, 复杂度为O(N^log(2, 2))=O(N)
  • 传送门 --> 使用递归查找最大值

小和

在一个数组中, 每一个数左边比当前数小的数累加起来, 叫做这个数组的小和. 求一个数字的小和. 如[1, 3, 4, 2, 5]小和为16

  • 算法: 利用归并排序, 关键步骤res += (arr[lowPtr] < arr[hightPtr] ? arr[lowPtr] * (right - hightPtr + 1) : 0);其中arr[lowPtr] * (right - hightPtr + 1) 是关键, 和归并排序一样, 没有浪费之前的比较次数
    本题递归公式T(N) = 2*T(N/2) + O(N), 时间复杂度同归并排序, 同位O(N*logN)
  • 传送门 --> 小和

逆序对

在一个数组中, 左边的数如果比右边的数大, 则这两个数构成一个逆序对, 请打印所有逆序对

  • 算法: 与小和相同, 只是在merge时把数组从大到小排列, 关键步骤res += (vt[leftPtr] > vt[rightPtr] ? (right - rightPtr + 1) : 0);与上述相差大小号和乘一个数区别, 目前只能统计个数, 打印有些问题
  • 传送门 --> 逆序对
  • 牛客练习 --> 数组中的逆序对

荷兰国旗问题

给定一个数组arr, 和一个数num, 请把小于num的数放在数组的左边, 等于num的数放在数组的中间, 大于num的数放在数组的右边, 要求额外空间复杂度O(1), 时间复杂度O(N)

  • 算法: 准备三个游标leftPtr初始指向数组边界起始位置前一个元素即left-1, rightPtr初始指向末尾后一个元素right+1, index从头到尾遍历数组比较
    (1) 比num小, index指向元素和leftPtr指向的下一元素交换, index和leftPtr同时+1
    (2) 等于num, 只把index+1
    (3) 大于num, 把index元素和rightPtr指向的元素前一个交换, rightPtr-1, 由于不确定rightPtr指向元素和num的关系, index不变
  • 传输门 --> 荷兰国旗问题

数据流的中位数

实质利用堆排序找中位数

排序数组最大差值

给定一个数组, 求排序之后相邻两数的最大差值, 要求时间复杂度O(N), 且要求使用非基于比较的排序

  • 算法: 运用桶的概念. N个数, 准备N+1个桶, 最小值放0号桶, 最大桶放N号桶放N号桶;
    三个数组, 分别记录桶是否有值, 桶内最大值, 桶内最小值; 相邻两个数最大差值可能存在于两个非空桶之间, 也可能存在于空桶之间
  • 传输门 --> 排序数组最大差值

数据流中位数

如何可以得到数据流中排序后的中位数

用数组结构实现大小固定栈

用数组结构实现大小固定队列

  • 算法: 使用三个变量: start, end, size. 目的是为了是start和end解耦合, 使start只与size有关, end只与size有关.
    插入只与m_size和m_arraySize有关, 弹出只与m_size和0有关; 弹出直接弹出m_start位置的元素, 插入直接插入到m_end位置
    m_start的变化只与m_arraySize有关; m_end的变化只与m_arraySize有关. m_start与m_end都是+1递增
  • 传输门 --> 数组实现大小固定的队列

仅用队列结构实现栈结构

传送门 -->

仅用栈结构实现队列结构

双栈返回最小值

实现一个特殊的栈, 在实现栈的基本功能的基础上, 再实现返回栈中的最小元素的操作. 要求: 1. pop, push, getMin操作的时间复杂度都是O(1); 2. 设计的栈类型可以使用现成的栈结构

顺时针打印矩阵

给定一个整型矩阵matrix, 按照从外向里以顺时针的顺序依次打印出每一个数字. 例如:如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

旋转正方形

给定一个整型正方形矩阵matrix, 把该矩阵调整成顺时针转换90度的样子. 额外空间复杂度O(1)

  • 算法: 同顺时针打印矩阵一样, 抽象一个顺时针打印边框的函数, 从外到内依次调用这个函数.
  • 传送门 --> 旋转正方形矩阵

之字形打印矩阵

给定一个矩阵matrix, 按照"之"字形方式打印这个矩阵

  • 算法: 抽象一个打印斜行的矩阵, 通过一个布尔变量来判断从上到下还是从下到上, 调用函数多了些边界判断. x_1和y_1, x_2和y_2先后移动次序有很大关系, 先比较x_1再判断y_1, 先比较y_2再判断x_2,
  • 传输门 --> 之字形打印矩阵

行列有序的矩阵中查找

给定一个由N*M的整数型矩阵matrix和一个整数K, matrix的每一行和每一列都是排好序的. 实现一个函数, 判断K是否在matrix中. 要求时间复杂度O(N+M), 额外空间复杂度O(1)

  • 算法: 从矩阵的特性出发, 确定查找方法. 设置查找起始点设置为右上角
      若K小于右上角的数, 则不可能在当前列的下面, 左移起始点
      若K大于右上角的数, 则不可能在当前行的左边, 下移起始点
  • 传送门 --> 行列有序的矩阵中查找
  • 牛客练习 --> 二维数组中的查找

链表面试与笔试

链表问题往往在空间复杂度上下工夫, 时间复杂度基本是O(N)或O(N^2)
笔试: 目的最快把问题过掉, 不追求空间复杂度
面试: 和面试官聊空间复杂度

分别实现反转单向链表和反转双向链表函数

  • 算法: 分别定义两个指针, next和pre, 当head不为空时, next和pre都初始化为nullptr
    (1)next指向head下一节点
    (2)利用pre与head反转单/双链表
    (3)使pre指向head
    (4)使head指向next
  • 递归反转单链表:
    (1)递归到最后一个节点, 然后依次返回
    (2)当前节点的下一节点的next指向当前节点
    (3)当前节点的next域指向nullptr
  • 传送门 --> 反转单双链表
  • 牛客练习 --> 反转链表

判断一个链表是否是回文结构

给定一个链表的头结点head, 判断该链表是否是会问结构. 如: 1, 2, 1返回true; 1, 2, 2, 1返回true; 1, 2, 3返回false. 如果求时间复杂度O(N), 额外空间复杂度O(1)

  • 算法1: 使用栈把元素逆序, 遍历两次列表, 第一次压栈, 第二次和栈中元素对比是否相同, 时间复杂度O(N), 空间复杂度O(N)
  • 算法2: 使用快慢指针

单向链表划分

将单向链表按某值划分成左边小, 中间相等, 右边大的形式

复制含有随机指针节点的链表

两个连续相交问题

给定三角形ABC和一点P(x,y,z),判断点P是否在ABC内,给出思路并手写代码

  • 面积计算公式及浮点数的比较
#include <iostream>
#include <math.h>
using namespace std;
#define ABS_FLOAT_0 0.0001		// 浮点数比较
struct point_float {
    float x;
    float y;
};

float GetTriangleSquar(const point_float pt0, const point_float pt1, const point_float pt2) {
    point_float AB, BC;
    AB.x = pt1.x - pt0.x;
    AB.y = pt1.y - pt0.y;
    BC.x = pt2.x - pt1.x;
    BC.y = pt2.y - pt1.y;
    return fabs((AB.x * BC.y - AB.y * BC.x)) / 2.0f;
}

bool IsInTriangle(const point_float A, const point_float B, const point_float C, const point_float D){
    float SABC, SADB, SBDC, SADC;
    SABC = GetTriangleSquar(A, B, C);
    SADB = GetTriangleSquar(A, D, B);
    SBDC = GetTriangleSquar(B, D, C);
    SADC = GetTriangleSquar(A, D, C);
    float SumSuqar = SADB + SBDC + SADC;
    if ((-ABS_FLOAT_0 < (SABC - SumSuqar)) && ((SABC - SumSuqar) < ABS_FLOAT_0))
        return true;
    else
        return false;
}

n个整数的无序数组,找到每个元素后面比它大的第一个数,要求时间复杂度为O(N)

vector<int> findMax(vector<int>num)
{
    if(num.size()==0)return num;
    vector<int>res(num.size());
    int i=0;
    stack<int>s;
    while(i<num.size())
    {
        if(s.empty()||num[s.top()]>=num[i])
            {
            s.push(i++);
            }
        else
        {
            res[s.top()]=num[i];
            s.pop();
        }
    }
    while(!s.empty())
    {
        res[s.top()]=INT_MAX;
        s.pop();
    }
    for(int i=0; i<res.size(); i++)
        cout<<res[i]<<endl;
    return res;
}
posted @ 2019-04-26 09:47  张飘扬  阅读(594)  评论(0编辑  收藏  举报