总结一些常用数据结构与算法

算法

    • 数组/字符串
      • 求(符合要求的)最大子区间/区间大小
      • 求最大子区间乘积
      • 数组中出现的数字
      • 接雨水
      • 柱状图中的最大矩形
      • 一串数字能够表示多少字母/IP地址
      • 第一个某元素
      • 排列
      • 第K大的某元素
    • 求子区间和相关的问题
      • 子序列和
      • 股票
    • 二叉树
      • 遍历
      • 深度
      • 二叉树的子结构
      • 根据前序遍历与中序遍历构建二叉树
      • 二叉搜索树
      • 平衡二叉树
      • 最近公共祖先
      • 路径和
      • 二叉树的镜像
      • 二叉树的对称
    • 链表
      • 删除结点
      • 从尾到头打印链表
      • 修改链表——翻转链表
      • 翻转链表
      • 寻找链中点
      • 链表排序
      • 重排链表
      • 环形链表
      • 克隆复杂链表(有两个指针)138
      • 用队列实现栈
    • 队列
      • 用栈实现队列
      • 优先级队列
      • 克隆图
      • 遍历图
    • 其他
      • LRU缓存机制
    • N进制
      • 给定数字返回字母序号
      • 给定字母序号返回数字
    • 位运算
      • 目标数组全子集
      • 目标数1的个数
      • 数组中数字出现的次数
      • 数值的整数次方pow(x,n)
      • 不用加减乘除做加法
    • 动态规划
      • 坐标型(能不能)
      • 数量型(方案数)
    • 排序
      • 计数排序
      • 冒泡
      • 选择排序
      • 归并
      • 快排
      • 堆排序
      • 希尔排序
    • 搜索
      • dfs
      • bfs
    • 查找
      • 二分查找
      • 哈希表查找
    • 回溯
      • 全排列
      • 全子集
      • 在字母矩阵中找目标单词
      • 机器人能到达的矩阵位置(坐标按位和小于等于K)
    • 递归
      • 一般需要建立一个递归函数(void),用于自循环
      • 递归三要素
    • 常考题
      • 接雨水
      • 递归找零钱
      • ip地址区分方式93
    • 一些函数
      • 排序sort(nums.begin(),nums.end())
      • 把字符串转换成十进制数字(int)stoi(string)
    • 注意事项
      • 变量初始化!
      • 变量声明位置(若在循环里则循环外是看不到的)
      • 使用一个vector可以不设定大小,但是如果函数中有对于vector[i]的判断则必须初始化大小
    • 区别
      • break与continue

数组/字符串

求(符合要求的)最大子区间/区间大小

求区间大小的时候,可以直接设置一个res(int),每次计算的时候如果大于res,则动态更新,否则res不变
求区间是什么的时候,需要设置一个vector temp与vector res,当temp.size()>res.size()的时候更新res

  • 子区间为回文串

    • 大的回文串需要建立在小的回文串上,按照子区间长度动态规划
  • 子区间为上升区间

    • 动态规划,dp[i]存储截止i为止的最长上升序列

      • 明确动态护规划关系 if(nums[j]
  • 无重复的最长子串

    • 需要用到一个set查重,有重复的left+1,无重复的right+1
    • 滑动窗口(哈希表+左右指针,如果不存在加入哈希表,右指针右移,如果存在且在左指针右边就更新左右指针及长度)
  • 重复的最长子串(可以修改K个字符)

    • 动态更新vec_temp,如果vec_temp中非N的个数大于K就删掉vec_temp中第一个元素

求最大子区间乘积

  • 用两个变量分别存储max_res与min_res,
    每次更新last_max_res=max_res; last_min_res=min_res;
    max_res=max(last_max_resnums[i],last_min_resnums[i],nums[i]);
    min_res=min(last_max_resnums[i],last_min_resnums[i],nums[i]);
    求最大max_res

数组中出现的数字

  • 数组中的重复数字

    • 1.时间优先,哈希表 时间On,空间On
      2.空间优先,鸽巢算法,时间On,空间O1,(改变了数组)
      3.不能改变数组的空间优先,范围二分,只适用于n个数,它们的范围是1~n-1,即可以通过某范围内数字的数量判断其中是否有重复的,时间Onlogn,空间O1,未改变数组

      //空间优先O1,时间Onlogn,未改变了数组,二分法,
      //应用条件:n大小的数组每个数的值都在1-n-1中间,可以根据某个范围内数的总数判断该区间是否有重复的数,
      //如果每个数的值在0-n-1中间,则不能用此方法判断

接雨水

  • 找到某点左右的最大值,该值取最小就是该点所能装的深度,该值减去该点的实际高度就是该点所能装的雨水量
    左最大值1n-1,右最大值n-20

柱状图中的最大矩形

  • 单调栈,某元素比栈顶元素大,放入;某元素比栈顶元素小,while把栈顶元素pop出,这时候得到了该栈顶元素为最高点的矩形大小

一串数字能够表示多少字母/IP地址

  • IP

    • dfs,递归,一共分四层,每层需要判断该层的分配方式能否成立以及该层是不是ip地址,如果可以计算下一层,如果不可以continue
  • 字母

    • dp[i]=dp[i-1]+dp[i-2];(如果本位为0则第一项为0,如果nums[i-1]与nums[i]组成的数字大于26则第二项为0)

第一个某元素

  • 排序数组
  • 旋转数组

排列

  • 下一个排列

    • 1.从右边开始,找到第一个比右边数小的数,有进入2,没有进入3
      2.从右边开始,找到第一个该数大的数,交换
      3.从小到大重排列

第K大的某元素

  • 中位数

    • n为奇数k=n/2+1,即是数组中位数
      n为偶数,k1=n/2与n/2+1两个数的平均数是中位数

      • 快排
  • 优先级队列(on),空间on

  • 排序找第N个数(nlogn),空间O1

求子区间和相关的问题

子序列和

  • 前缀序列和
  • 递归

股票

  • 只能买卖一次,求出每个股票与前一天的差,求该数组的最大连续区间和

    • 求前缀序列和的时候:
      1.原数组size=n
      2.前缀和序列的大小也为n,第一位为nums[0];
  1. int pre_sum=0;
     int res=INT_MIN;//(允许结果为负,否则res=0)
     int min_pre_sum=0; //保证初始的时候sum为0,才可以把第一个数也加 
     进去,计算res的时候也得先算一下第一个数    
     for(int i=0;i
  • 无限制买卖,可以用动态规划dp[i][0]与dp[i][1]表示当天是否持有

  • 只买卖两次

    • 分区间,复杂度On^2

      • 求两个区间的最大差
    • 其他

二叉树

遍历

  • 递归

  • 非递归

    • dfs

      • 前序遍历

        • 用一个栈,循环条件为cur非空与栈非空,第一次遇到的时候输出到vector
      • 中序遍历

        • 第二次的时候输出
      • 后序遍历

        • 新建一个结构体,为每个节点加一个flag标识,第三次的时候输出
    • bfs

      • 分行的层序遍历

        • 队列

          • 是一个二维数组,每层输出长度为上层遍历完后的队列长度len=q.size();//q的大小是动态变化的,必须设定好
      • 不分行的层序遍历

        • 空的写上null,每个节点要多判断一步该节点是否为空,字符串的末尾去掉一个“,”
      • 之字形层序遍历

深度

  • 最大深度

    • 分治递归

      • max(leftDepth,rightDepth)+1
    • 层序遍历求层数

      • 注意层数与迭代次数的关系:最后为空的时候还会算一层,只是没有再往队列里加元素了
  • 最小深度

    • 三种情况

      • 左子树与右子树都有,左子树为空,右子树为空

二叉树的子结构

  • 采用递归判断,如果节点数是double,不能直接判断两者是否相等,而是要写一个equal函数,判断两者的差是否小于0.0000001

根据前序遍历与中序遍历构建二叉树

  • 递归,注意递归终止条件

二叉搜索树

  • 左节点(包括左节点的所有子节点)小于根节点,右节点(包括右节点的所有子节点)大于根节点

    • 判断一个树是否是二叉搜索树

      • 中序遍历求是否递增

平衡二叉树

  • 任意节点左右深度差小于等于1

    • 判断一个树是否是平衡二叉树

      • 递归求左右子树高,相差超过一则为-1,不超过1就取max

最近公共祖先

  • 最大/小的K个数(流)

路径和

二叉树的镜像

  • 递归交换左右节点

二叉树的对称

  • 递归检测两个该函数,检查他们的左右节点与右左节点是否相等

链表

删除结点

  • 删除值为n的结点
  class Solution {//遍历一边,递归
  public:
      ListNode* deleteNode(ListNode* head, int val) {
          if(head= =nullptr)
              return nullptr;
          if(head->val==val)
              return head->next;
          head->next=deleteNode(head->next,val);
          return head;
      }
  };
- 注意是否为最后一个结点
  • 删除链表的倒数第K个节点

    • 1.寻找倒数第K+1个节点,注意倒数第K个节点可能为头结点,所以需要dummyNode
      2.nPre->next=nPre->next->next
  • 删除排序链表重复的节点(留一个)

    • 1.ListNode*temp=cur->next;cur->next=cur->next->next,delete temp;
      2.发生重复(cur->next->val==cur->val),此时cur不动,可能有多个重复
      3.未发生重复 ,cur=cur->next;
  • 删除排序链表重复的节点(都删除)

    • 1.三个指针,cur,left=cur->next,right=left//第一个元素可能被删除,所以cur初始为dummy,right的下一个值如果和它相同,就继续往后移,直到right下一个为空或者值不同,
      2.此时,如果right==left,则cur=cur->next;否则,cur->next=right->next;

从尾到头打印链表

  • 不修改链表——利用一个栈

    • 放入指针与链表节点是不一样的,建栈时要注意

修改链表——翻转链表

翻转链表

  • 从头到尾

    • 需要三个结点:while(cur)
      1.ListNode cur_pre=nullptr(初始为空,因为反转后需要尾结点为空)
      2.ListNode cur=head(从头开始)
      3.ListNode cur_next
      最后导出的是cur_pre(在最后一次中cur_pre=cur,而cur=cur_next已经为空)
  • 从m到n

    • 与反转类似
      1.寻找m点的前一个点m_pre与m_node,注意当m=1时该点应该为dummy node
      2.翻转m-n的链表
      3.m_pre->next=pre;m_node->next=cur;

寻找链中点

  • fast=head->next;slow=head; //如果是fast=head得到结果偏后
    while(fast&&fast->next)
    {fast=fast->next->next;slow=slow->next}return slow

链表排序

  • 一个未排序链表

    • 归并排序
      1.寻找中间节点(应该是偏前的,这样容易断开他之后的,fast=head->next),并断开它和它之后的联系
      2.递归调用,左右链表为调用(head1)与(head2)的结果,终止条件是head仅剩一个节点if(!head||!head->next)return head;
      3.合并左右
  • 两个排序链表

  • K个排序链表


```cpp
  class Solution {
  public:
      ListNode* mergeKLists(vector<ListNode*>& lists) {
          int n=lists.size();
          if (n = =0)
              return nullptr;
          return mergeHelper(lists,0,lists.size()-1);
      }
  private:
      ListNode*mergeHelper(vector<ListNode*>& lists,int left,int right)
      {
          if(left==right)
              return lists[left];            
          int mid=left+(right-left)/2;
          ListNode* l1=mergeHelper(lists,left,mid);
          ListNode* l2=mergeHelper(lists,mid+1,right);
          return mergeTwo(l1,l2);
      }
      ListNode* mergeTwo(ListNode* l1,ListNode* l2)
      {
          ListNode*dummyNode=new ListNode(0);
          ListNode*temp=dummyNode;
          while(l1&&l2)
          {
              if(l1->val<l2->val)
              {
                  temp->next=l1;
                  l1=l1->next;
              }
              else
              {
                  temp->next=l2;
                  l2=l2->next;
              }
              temp=temp->next;
          }
          if(l1)
          {
              temp->next=l1;
          }
          if(l2)
          {
              temp->next=l2;
          }
          return dummyNode->next;
      }
  };

重排链表

  • 前后交替排列
    1-2-3-4-5 -> 1-5-2-4-3

    • 1.找到中间节点(靠后的中间节点,并与前链断开,需要slow靠前,fast=fast->next)//为了合并方便,前链大于等于后链
      2.翻转后面链表
      3.合并两个链表(前链大于等于后链方便)

环形链表

  • 判断环形链表

    • 1.快慢指针,while(fast!=slow)
      2.if (fast== nullptr||fast->next==nullptr) return false;
      3.return true
  • 求环形链表入口

    • 4.while(slow->next!=head)head=head->next,slow=slow->next;
      5.return head;

克隆复杂链表(有两个指针)138

用队列实现栈

队列

用栈实现队列

优先级队列

//升序队列,顶上的数为最小值
priority_queue q;

//降序队列 ,顶上的数为最大值(默认是这个)
priority_queue q;

删除增加的时间复杂度为o(logn),n为队列大小

  • 最大/最小的K个数,最大最小的第K个数

    • 找最小的K个数,维护一个大根堆(默认 priority_queue
      找最大的K个数,维护一个小根堆(priority_queue

克隆图

遍历图

其他

LRU缓存机制

N进制

给定数字返回字母序号

十进制转换为N进制

  • 1.while(n)-> n=n-1; string+=n%26+‘A’ ,n/=26;
    2.reverse(string.begin(),string.end())

给定字母序号返回数字

N进制转换为十进制

  • 1.从最左边开始,res+=string[i]-‘A’+1
    2.res=res*26;

位运算

五种运算:
并,或,异或,右移,左移(分类)

负数的二进制为按位取反+1

目标数组全子集

  • 按次数递归,一共递归n次,每次两种选择,放nums[i]或者不放
  • 转换成二进制数字

目标数1的个数

  • 设置标志位flag=1,最好是unsigned int
    while(flag) ->if(flag&n) count++ ->flag=flag<<1;
  • while(n) -> n=n&(n-1) count++;

数组中数字出现的次数

  • 有1/2个出现一次,其余出现两次

    • 1.所有数求异或=a^b
      2.找到异或结果的某一位(第一位)为1的记为flag
      3.按照这个flag分类(与该flag相与结果为1或者0)
      4.res1=0;res1^=第一类,第二类同理得到res2
  • 有1个出现一次,其余出现三次

int singleNumber(vector<int>& nums) {
          //特殊情况
          //一般情况
          vector<int> bit_num(32,0);
          for(auto n:nums)
          {
              unsigned int flag=1; //不支持负值左移
              for(int i=31;i>=0;i--)
              {
                  int bit=n&flag;
                  if(bit!=0) //存储顺序要和解码顺序相反
                  {
                      bit_num[i]+=1;
                  }
                  flag=flag<<1;
              }
          }
          //解过程
          int res=0; //负数也没问题,按照res一直左移,会使最高位变成1,得到的数就负的
          for(int i=0;i<32;i++)
          {
              res=res<<1;
              res+=bit_num[i]%3;
              
          }
          return res;
      }
- 1.没有办法用异或

2.开辟一个数组bit_num,大小32,每个数每一位的个数存储上去
3.解码顺序与存储顺序相反,res=0;res+=bit_num[i]%3;res<<1;i++

数值的整数次方pow(x,n)

double help(double x,long num) //处理n>0的情况
    {
        double res=1.0;
        while(num)
        {
            if(num&1)
                res=res*x;
            x=x*x;
            num=num>>1;
        }
        return res;
    }
  • 指数=0,<0,>0

    • 1.当n<0时,需要计算n的相反数,但是INT_MIN的相反数不在INT范围中,所以需要先将n转换为long在求相反数
      2.while(n) if(n&1)->ans*=x
      ,x*=x,n=n>>1,

不用加减乘除做加法

int add(int a, int b) {
        while(b)
        {
            int temp=a^b;
            b=((unsigned int)a&b)<<1;
            a=temp;
        }
        return a;
    }

- 分为3while(b)
1.求temp=a^b
2.b=求a,b与左移13.a=temp
return a;

动态规划

坐标型(能不能)

  • 一维坐标
  • 二维坐标

数量型(方案数)

  • 上楼梯/斐波那契数列

    • 自底向上

      • 开一个dp[n],注意n0,或者n1的时候可能dp[n]越界问题
      • 开两个变量,储存dp[i-1与dp[i-2]],每次更新这两个变量,注意顺序
  • 解码字母

class Solution {  //dp[i]代表前i个元素,比上一种好
  public:
      int numDecodings(string s) {
          int n=s.size();
          //特例
          if(n==0)
              return 0;
          if(n==1)
          {
              if(s[0]=='0')
                  return 0;
              else
                  return 1;
          }
          vector<int>dp(n+1,0); //dp[i]代表前i个元素
          dp[0]=1;
          for(int i=1;i<=n;i++)
          {
              //dp[i]=dp[i-1]+dp[i-2];
              dp[i]+=s[i-1]=='0'?0:dp[i-1];
              if(i>=2)
              {
                  if(s[i-2]=='1'||(s[i-2]=='2'&&s[i-1]<='6'))
                      dp[i]+=dp[i-2];
              }
          }
          return dp[n];
      }
  };
  • 找零钱

    • 能凑出零钱的最少零钱个数

      • dp=min(dp[i-coin[j]+1]) //dp[i-coin[j]]!=-1
    • 凑出该零钱的方案

  • 买门票

    • 与找零钱的区别在于如果当天天数小于门票的天数,仍可以购买该长度的门票

      • if dp[i]==1,dp=min(dp[i-ticket_day[j]+ticket_cost[j]])
        else dp[i]=dp[i-1]
  • 切绳子

    • dp[i]=max(dp[j]*dp[i-j])
      //j必须从1开始,代表切分一次
      //j计算到i/2就可以了,后面重复
      //dp必须从4开始算,前面的给值
  • 分割字符串

排序

计数排序

实际问题,重复多,范围小
时间复杂度ON,空间复杂度OM(非重复元素个数,最大为n)

//给员工年龄排序,时间复杂度on,空间复杂度oM(M为数组中数的大小)

class solution
{
public:
    void sort(vector<int>&nums)
    {
        int n=nums.size();
        if(n==0||n==1)
            return ;
        int oldestAge=99;
        vector<int>age_num(oldestAge+1,0);
        for(int i=0;i<n;i++)
        {
            age_num[nums[i]]++;//统计每个年龄有多少人
        }
        int cur=0;
        for(int i=1;i<=oldestAge;i++)
        {
            for(int j=0;j<age_num[i];j++)
            {
                nums[cur++]=i;
            }
        }
    }
};
  • 假设数组的范围容易确定,
    1.设置一个数组.size=max+1
    2.确定每个元素出现的数量
    3.将结果导出

冒泡

class solution1_1
{
public:
    vector<int>sort(vector<int>&nums)
    {
        if(nums.size()==0||nums.size()==1)
            return nums;
        int n=nums.size();
        for(int i=0;i<n-1;i++)
        {
            bool isSort=true;
            for(int j=n-1;j>i;j--)
            {
                if(nums[j-1]>nums[j])
                {
                    swap(nums[j-1],nums[j]);
                    isSort=false;
                }
            }
            if(isSort==true)
                return nums; 
        }
    return nums;
    }
};
  • 1.外层从前往后,里层从后往前,每次将最小的放到最前面
    2.设置标志位,如果没有发生交换则直接导出

选择排序

class solution0
{
public:
    vector<int>sort(vector<int>&nums)
    {
        for(int i=0;i<nums.size()-1;i++)
        {
            int min_temp=i;
            for(int j=i+1;j<nums.size();j++)
            {
                if(nums[j]<nums[min_temp])
                {
                    min_temp=j;
                }
            }
            if(min_temp!=i)
            {
                swap(nums[i],nums[min_temp]);
            }
        }
        return nums;
    }
};
  • 每次将最小的数移动到最前面
    通过设置一个min_temp=i,
    if(nums[j]min_temp=j),
    if(i!=min_temp)swap(nums[i],nums[min_temp])

归并

class solution2
{
public:
    vector<int>sort(vector<int>&nums)
    {
        int n=nums.size();
        if(n==0||n==1)
            return nums;
        mergeSort(nums,0,n-1);
        return nums;
    }
private:
    void mergeSort(vector<int>&nums,int left,int right)
    {
        vector<int>temp(right-left+1);
        //vectortemp;
        if(left>=right)
            return ;
        int mid=(right-left)/2+left;
        mergeSort(nums,left,mid);
        mergeSort(nums,mid+1,right);
        //合并两个排序数组
        int i=left;
        int j=mid+1;
        int cur=0;
        while(i<=mid&&j<=right)
        {
            if(nums[i]<nums[j])
            {
                //temp.push_back(nums[i]);//如果用push,vector初始就不要设置大小
                temp[cur++]=nums[i++];
                //i++;
            }
            else
            {
                temp[cur++]=nums[j++];
                //temp.push_back(nums[j]);
                //j++;
            }
        }
        while(i<=mid)
        {
            temp[cur++]=nums[i++];
            //temp.push_back(nums[i]);
            //i++;
        }
        while(j<=right)
        {
            temp[cur++]=nums[j++];
            //temp.push_back(nums[j]);
            //j++;
        }
        for(int i=0;i<right-left+1;i++)
        {
            nums[i+left]=temp[i];
        }
        return;     
    }
};
  • 排序

    • 数组

      • 1.递归,注意递归终止条件
        2.两个while保证所有的数都被导入到新数组中
    • 链表

  • 合并两个排序数组

    • 两个while
  • 合并两个排序链表

    • while+if
  • 合并n个排序数组

  • 合并n个排序链表

快排

  • 快排算法
class solution3
  {
  public:
      vector<int>sort(vector<int>&nums)
      {
          int n=nums.size();
          if(n==0||n==1)
              return nums;
          srand((unsigned)time(NULL));//时间种子
          quick_sort(nums,0,n-1);
          return nums;
      }
  private:
      void quick_sort(vector<int>&nums,int left,int right)
      {
          if(left>right)
              return ;
          int pos=random_partition(nums,left,right);
          quick_sort(nums,left,pos-1);
          quick_sort(nums,pos+1,right);
      }
      int random_partition(vector<int>&nums,int left,int right)
      {
          int ran=rand()%(right-left+1)+left;
          int pivot=nums[ran];
          swap(nums[ran],nums[right]);
          int i=left;
          for(int j=left;j<right;j++)
          {
              if(nums[j]<pivot)
              {
                  swap(nums[i],nums[j]);
                  i++;
              }
          }
          swap(nums[i],nums[right]);
          return i;
      }
  };
- 需要一个时间种子srand((unsigned)time(NULL)
  • 寻找第K小/大的数
class Solution {
  public:
      int findLeastk(vector<int>& nums,int k) 
      {
          srand((unsigned)time(NULL));
          return quickSelect(nums,0,nums.size()-1,k);
      }
  private:
      int quickSelect(vector<int>&nums,int left,int right,int k)
      {
          if(left==right)
              return nums[left];
          int pos=random_partition(nums,left,right);
          if(pos==k)
              return nums[pos];
          else if(pos<k)
              return quickSelect(nums,pos+1,right);
          else
              return quickSelect(nums,left,pos-1);
      }
      int random_partition(vector<int>&nums,int left,int right)
      {
          int ran=rand()%(right-left+1)+left;
          int pivot=nums[ran];
          swap(nums[ran],nums[right]);
          int i=left;
          for(int j=left;j<right;j++)
          {
              if(nums[j]<pivot)
              {
                  swap(nums[i],nums[j]);
                  i++;
              }
          }
          swap(nums[i],nums[right]);
          return i;
      }
  };
- quickSelect与quickSort不同,

quickSelect:if(left==right)return nums[left]为k位置的数
quickSort:if(left>right)return;//终止递归
k=0,代表最小的数
k=n-1,代表最大的数

  • 中位数,时间复杂度oN
    (数组中出现超过一半的数字)

    • 数组大小为奇数,寻找第n/2+1的数,相当于K=N/2
      数组大小为偶数,寻找第N/2与n/2+1的数,相当于k1=n/2-1 ,k2=n/2,
      由于两个数取平均数可能为小数,
      double res=((double)res1+(double)res2)/2 //不能再用位运算
    • 抛区间法
  • 二路快排

  int random_partition(vector<int>&nums,int left,int right)
      {
          int ran=rand()%(right-left+1)+left;
          int pivot=nums[ran];
          swap(nums[ran],nums[left]);
          int i=left+1;
          int j=right;
          while(true)
          {
              while(nums[i]<pivot&&i<=right)
                  i++;
              while(nums[j]>pivot&&j>=left+1)
                  j--;
              if(i>j)
                  break;
              swap(nums[i],nums[j]);
              i++;
              j--;
              
          }
          swap(nums[left],nums[j]);
          return j;
      }
- 当数组内重复元素较多时,每次partition的时候总是把大于等于pivot的数都排到右边,所以数组内两边不平衡,退化为N^2

改进,partition的时候,将小于pivot的放在左边,大于pivot的放在右边,让数组尽可能平衡

	- 易错点

1.int i=left+1,int j=right
2.while(true)循环终止条件if(i>j)break

  • 三路快排
 int random_partition(vector<int>&nums,int left,int right)
      {
          int ran=rand()%(right-left+1)+left;
          int pivot=nums[ran];
          swap(nums[ran],nums[left]);
          int i=left+1;//等于pivot的
          int j=left; //小于pivot的
          int k=right+1; //大于pivot的
          while(i<k)
          {
              if(nums[i]<pivot)//小于pivot的
              {
                  swap(nums[i],nums[j+1]);
                  i++;
                  j++;
              }
              else if(nums[i]>pivot)//大于pivot的
              {
                  swap(nums[i],nums[k-1]);
                  k--;
              }
              else//等于pivot的
              {
                  i++;
              }  
          }
          swap(nums[left],nums[j]);
          return j;
      }
  • 分成三份,小于pivot,等于pivot和大于pivot

易错点
1.int i=left+1,int left_part=left,int right_part=right+1
2.循环终止条件while(i 3.swap(nums[i],nums[left_part++]),swap(nums[i],nums[right–]
4.swap(nums[left],nums[left_part]);
5.return left_part;

堆排序

//堆排序,3个步骤,1.建立大根堆 2,将堆顶元素放到最后3,将剩下的变成大根堆
class solution
{
public:
    void sort (vector<int>&nums)
    {
        int n=nums.size()-1;
        buildHeap(nums,n);
        for(int i=n;i>0;i--)
        {
            swap(nums[0],nums[i]);
            //n--;maxHeapify(nums,0,n);
            maxHeapify(nums,0,i-1);
        }
        return ;
    }
private:
    void buildHeap(vector<int>&nums,int n)
    {
        for(int i=n>>1;i>=0;i--)
        {
            maxHeapify(nums,i,n);
        }
        return;
    }
    void maxHeapify(vector<int>&nums,int i,int n)
    {
        for(;(i<<1)+1<=n;)
        {
            int lson=(i<<1)+1;
            int rson=(i<<1)+2;
            int large=0;
            if(lson<=n&&nums[lson]>nums[i])
                large=lson;
            else
                large=i;
            if(rson<=n&&nums[rson]>nums[large])
                large=rson;
            if(large!=i)
            {
                swap(nums[i],nums[large]);
                i=large;
            }  
            else
                break;
        }
        return;
    }
};
  • 三个部分
    1.建堆,需要nums与规模n(nums.size()-1),分层建堆i=nums.size()/2->0
    2.每层堆化,需要nums与规模n和开始位置i
    3.先将数组建堆,每次将最大的(nums[0])换到最后,n–重新将堆首堆化(i=0)

    • 易错点
      1.每行堆化的时候for(;(i<<1)+1<=n;) 这里i没有初始值和每次的递增++
      2.注意,x*2是x<<1,别写反了
      3.每次把堆首移到最后,需要n–,在将剩下的最大堆化maxHeapify(nums,0,n);

希尔排序

搜索

dfs

bfs

查找

二分查找

  • 排序一维区间

    • 第一个/最后一个
  • 二维数组

    • 整体排序二维数组
 vector<int> findTarget(vector<vector<int>>& nums,int target)
	      {
	          vector<int>res;
	          if(nums.size()==0)
	          {
	              res={-1,-1};
	              return res;
	          }
	          int m=nums.size();
	          int n=nums[0].size();
	          int start=0;int end=n*m-1;
	          int mid=(start+end)>>1;
	          if(nums[mid/n][mid%n]<=target)
	              start=mid;
	          else
	          {
	              end=mid;
	          }
	          if(nums[start/n][start%n]==target)
	          {
	              res={start/n,start%n};
	              return res;
	          }
	          if(nums[end/n][end%n]==target)
	          {
	              res={end/n,end%n};
	              return res;
	          }
	          res={-1,-1};
	          return res;
	      }
- 每行递增,每列递增

	- 每次选取的数是最右上角的数

if(nums[i][j]>target)//说明这一列都大于
column–; //列数
else row++;
等于直接出结果

  • 不确定数组长度

  • 不确定目标(旋转数组)

    • 旋转数组的最小值
      (数组内的值可能重复)

      • 1.while循环的时候每次更新target=nums[end],并且要考虑到target在循环开始前的初值(n==2时循环不会开始)
        2.如果nums[mid]=targe,这时候说明后面都是重复的,需要end–,而不是得到结果
        4.结束条件得再nums[start]与nums[end]中选一个最小的,而不是前一个!,因为这不是排序数组,可能前一个等于target而后一个才是最小的
    • 旋转数组中的某一个值

while(start+1<end)
	          {
	              int mid=(start+end)>>1;
	              if(nums[start]<=nums[mid]) //首先判断在那一列上
	              {
	                  if(nums[mid]==target)
	                      return mid;
	                  else if(nums[start]<=target&&target<nums[mid])//其次判断在该列的一般区间上,是的话该该列的方向缩进
	                      end=mid;
	                  else
	                      start=mid;
	              }
	              else
	              {
	                  if(nums[mid]==target)
	                      return mid;
	                  else if(nums[mid]<target&&target<=nums[end])
	                      start=mid;
	                  else
	                      end=mid;
	              } 
	          }
	- 分为两种情况,前一列后一列

1.判断在哪一列上,根据if(nums[start]<=nums[mid])
2.在第一列上,if(nums[start]<=target 在第二列上,if(nums[mid]

  • 求sqrt x

    • 整数

      • 相当于target=x/mid,求最后一个小于等于这个的num
        注意:判断的时候不可以写mid*mid与x比较,可能会越界
    • 小数

 double sqrt (double x)
	      {
	          double res=0;
	          //特殊情况
	          if(x==0)
	              return 0;
	          if(x==1.0)
	              return 1.0;
	          //正常情况
	          double start=0;
	          double end=0;
	          if(0<x&&x<1.0)
	          {
	              start=0;
	              end=1.0;
	          }
	          if(x>1)
	          {
	              start=1.0;
	              end=x;
	          }
	          while(start+1e-6<end)
	          {
	              double mid=start+(end-start)/2;
	              if(x/mid==mid)
	                  return mid;
	              else if(x/mid>mid)
	                  start=mid;
	              else
	                  end=mid; 
	          }
	          if(x/end>=end)
	              return end;
	          else
	              return start; 
	      }
	- while(start+1e-6

不能判断小数是否相等,设置一个equal函数,如果两个double或者float的差小于1e-7就认为他们相等

		- 01

			- start=1;,end=x

哈希表查找

回溯

全排列

  • 无重复元素

    • 回溯终止条件:temp.size()=nums.size
      判断合法条件:temp中没有该元素
  • 有重复元素(结果不能重复)

    • 1.数组需要先排列
      2.需要一个hash,可以是数组,可以是set
      3.多一步判断条件:if(i!=0&&nums[i]==nums[i-1]&&hash[i-1]==0)//hash.count(i-1)==0

全子集

  • 无重复

    • 按位与
      一共的可能情况共2^n种,设置一个i++
      让每个i与(1<

    • bfs回溯,每次选择放或者不放
      设置index位,达到nums.size时将temp放入res

      • vector.push_back(temp);
        vector.pop_back();
    • dfs回溯,每次遍历从index到nums.size-1,选择放或者不放,放的话将temp->res,当index=nums.size()时return

  • 有重复

    • dfs回溯+剪枝,每次遍历从index到nums.size-1,选择放或者不放,放的话将temp->res,当index=nums.size()时return
      剪枝条件,当index>i并且nums[i]=nums[i-1];

在字母矩阵中找目标单词

  • 设定一个index,作为哈希,表示寻找的是单词的index坐标的数;设定两个坐标量cow与column
    1.结束条件:index=word.size()
    2.回溯失败条件1:cow<0||cow>=nums.size()||column…
    回溯失败条件2:nums[cow][column]!=word[index]
    回溯失败条件3:往剩下四个方向寻找下一步都失败
    3.回溯成功…

机器人能到达的矩阵位置(坐标按位和小于等于K)

  • 1.一个数字的按位和
  int n;
  int res;
  while(n>0)
  {
  	res+=n%10;
  	n/=10;
  }
  return res;
  • 1.可以把结果作为引用&,每次递归更改
    2.或者把结果作为int,每次递归都返回

递归

一般需要建立一个递归函数(void),用于自循环

递归三要素

  • 解决什么问题
  • 递归关系
  • 递归终止条件(怎么跳出)

常考题

接雨水

递归找零钱

ip地址区分方式93

一些函数

排序sort(nums.begin(),nums.end())

把字符串转换成十进制数字(int)stoi(string)

把二进制字符串转换成十进制数字(int)stoi(string,0,2)//第一个数字是开始位置,第二个数字是二进制

注意事项

变量初始化!

  • 0
  • INT_MAX/INT_MIN

变量声明位置(若在循环里则循环外是看不到的)

使用一个vector可以不设定大小,但是如果函数中有对于vector[i]的判断则必须初始化大小

区别

break与continue

  • break:直接跳出循环,也不会执行最后的i++
  • continue:跳出本次循环,进行下一次循环前要执行最后的i++
  • 都只能跳出一层循环,如果跳出两层可以设置一个标志位

你可能感兴趣的:(算法,数据结构,数据结构,算法,队列)