数据结构与算法整理

                                    链表

(1)链表的技巧

  1. 快慢指针(找环,环入口,环长度)
  2. 双指针(倒数K个节点)
  3. 合并链表(递归求解)
  4. 约瑟夫环(环形链表)

(2)链表的基本操作

  1. 删除

  2. 删除重复节点

  3. 反转链表

树的基本遍历方法

1.前序
2.后序
3.中序
4.层序遍历
很多题目对树的操作其实就是对树的不同层次的遍历。
【例】判断二叉树是否是平衡二叉树(剑指 offer)
利用后序遍历的方式,即在访问某一个节点之前,该节点的左右子树已经被访问过了,此时可以将左右子树的深度进行判断是否在[-1,1]范围之内,同时将判断结果抛给上一层(递归调用),从而完成平衡二叉树的判断。

public class Solution {
    //后续遍历时,遍历到一个节点,其左右子树已经遍历  依次自底向上判断,每个节点只需要遍历一次
     
    private boolean isBalanced=true;
    public boolean IsBalanced_Solution(TreeNode root) {
         
        getDepth(root);
        return isBalanced;
    }
    public int getDepth(TreeNode root){
        if(root==null)
            return 0;
        int left=getDepth(root.left);
        int right=getDepth(root.right);
         
        if(Math.abs(left-right)>1){
            isBalanced=false;
        }

        // left,right是对当前节点的左右子节点进行遍历,return Math.max(left,right)+1
         // 则是求出当前节点的深度,并将结果抛给上一层。
        // 这三个步骤相结合则是一次后序遍历(左右根)
        return right>left ?right+1:left+1;
         
    }
}

【例】求解有序有叉树第k大的节点
这个题明显就是考察中序遍历(左中右)。那么有两中方式进行中序遍历:递归和迭代。通常迭代的难度要高于递归。迭代主要操作就是利用辅助栈来代替递归的栈帧调用。

(1)建立辅助栈。
(2)新见节点(node)= root。
(3)判断node节点的左子节点是否为空,如果不为空则将左子节点入栈,如此往复直到node节点为空。
(4)node节点为空时候,将栈顶元素赋值给node,并同时判断node的右子节点是否为空,不为空则将右子节点入栈。
(5)直到整个栈为空,则整个二叉树访问结束。

还原二叉树

1.前中
2.中后
这两种不管是哪一种都是中序遍历确定左、右子树的划分点(即root在哪里),以根据前中序结果为例:前序遍历(根左右)结果序列中第一个位置就是root节点,将此root节点带回中序遍历序列找出对应位置(x),那么可以找出左右子树(x的左边就是左子树,右边就是右子树),此时递归的进行如此判断则可将二叉树还原。

树的衍生

  1. 红黑树
    红黑树主要有5个特性:
    (1)只有两种颜色:红、黑
    (2)叶子节点都是黑色
    (3)红色节点的子节点的颜色必然为黑色
    (4)从某节点到所有叶子节点的所有路径上的黑色节点数目相同
    (5)根节点为黑色
    1.1 红黑树的操作
    插入
    按照插入节点大小划分,分三种情况插入。假定插入节点为T
    (1)T是最小的。
    (2)T是最大的。
    (3)T是中间大小的。
    总体过程如图所示:
    image.png

红黑树保持特性操作
(1)左旋:当从右侧插入节点后,节点是红色的,则需要左旋。
左旋步骤
1)将父节点(假设为r)右节点(即刚插入的节点,加上为a)指向a的左节点。
2)将a的左节点指向r。
3)给a赋予r的颜色,给r赋予红色(相当于新插入节点)。
(2)右旋:右旋是当节点的左子节点是红色,且左子节点的左子节点还是红色时。核心:左子节点调整到中心,而将中心节点变为左子节点的右子节点
(3)变色:发生在节点颜色为黑色,左右节点为红色的时候,进行颜色反转(变色)。

删除操作。

  1. B树

树的操作

  1. 删除
  2. 添加

树的性质

                                    算法

排序算法

排序分类

以快排为例,使用分别使用递归和迭代进行比较。
第一步先找基准点(通常以第一个数为基准点x),然后从后往前找小于x的数y,并放于x的位置,此时y所在的位置空出,这时候开始从后面开始往前去寻找大于x的数,将其放于y空出来的位置中,退出的时候x放在最后一个空出来的位置。
方法:使用两个指针,一个指针先从后往前搜索,找到y之后,另一个指针从前往后搜索,将找出来的数放在y空出来的位置。在退出时两个指针是相遇的,将基准点放回这个相遇的位置,这样第一遍交换就完成了。同时需要注意将第一遍交换后的基准点的位置返回,以便于后面的递归操作。

int AdjustArray(int s[], int l, int r) //返回调整后基准数的位置
{
    int i = l, j = r;
    int x = s[l]; //s[l]即s[i]就是第一个坑
    while (i < j)
    {
        // 从右向左找小于x的数来填s[i]
        while(i < j && s[j] >= x) 
            j--;  
        if(i < j) 
        {
            s[i] = s[j]; //将s[j]填到s[i]中,s[j]就形成了一个新的坑
            i++;
        }
 
        // 从左向右找大于或等于x的数来填s[j]
        while(i < j && s[i] < x)
            i++;  
        if(i < j) 
        {
            s[j] = s[i]; //将s[i]填到s[j]中,s[i]就形成了一个新的坑
            j--;
        }
    }
    //退出时,i等于j。将x填到这个坑中。
    s[i] = x;
 
    return i;
}

完成了第一遍交换之后,后面对第一遍交换分出来的左右两块分别进行这样的交换。这个操作可以通过递归来完成。


void quick_sort1(int s[], int l, int r)
{
    if (l < r)
    {
        int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[]
        quick_sort1(s, l, i - 1); // 递归调用 
        quick_sort1(s, i + 1, r);
    }
}

复杂度分析

使用场景

动态规划

动态规划大体上分这么几类:
(1)组合问题
其中比较经典的就是:1. 分钱币问题(背包问题)

  1. 掷骰子问题
  2. 爬台阶的组合

(2)最优化问题
较为经典的是:

  1. 剪绳子
    dp[i]代表长度为i的绳子能够被剪获得的最大值。
  2. 最大连续子序列和的问题
int maxsequence3(int a[], int len)  
{  
    int maxsum, maxhere;  
    maxsum = maxhere = a[0];   //初始化最大和为a【0】  
    for (int i=1; i<len; i++) {  
        if (maxhere <= 0)  
            maxhere = a[i];  //如果前面位置最大连续子序列和小于等于0,则以当前位置i结尾的最大连续子序列和为a[i]  
        else  
            maxhere += a[i]; //如果前面位置最大连续子序列和大于0,则以当前位置i结尾的最大连续子序列和为它们两者之和  
        if (maxhere > maxsum) {  
            maxsum = maxhere;  //更新最大连续子序列和  
        }  
    }  
    return maxsum;  
}  

【例】[乘积最大子序列]
由于本题中有可能出现负数,那么乘积最大就有可能就是两个负数之积,如此则有必要对负数的乘积进行记录。所以在下面的代码中minSub[i]就是记录到i时的乘积最小值,如果i之前都是正数,那么minSub[i]就会记录当中最小的数(包括当前值nums[i]),如果i之前当中出现了一次负数,那么minSub[i]会记录到一直相乘到当前值nums[i]的乘积。如果出现了2次负数,那么负负得正,将此正数与nums[i]相比较取得最小的那一个。

class Solution {
    public int maxProduct(int[] nums) {
        // 本题目由于含有负数,所以最大值有可能是两个负数相乘得到
        int[] maxSub = new int[nums.length];
        int[] minSub = new int[nums.length];
        int global = nums[0];
        maxSub[0] = minSub[0] = nums[0];
        
        for(int i = 1;i < nums.length;i++){
            if(nums[i] > 0){
                maxSub[i] = Math.max(maxSub[i - 1]*nums[i],nums[i]);
                minSub[i] = Math.min(minSub[i - 1]*nums[i],nums[i]);
            }else{
                // nums[i]的符号不同的时候,最大值、最小值的构成也会相反。
                maxSub[i] = Math.max(minSub[i - 1] * nums[i],nums[i]);
                minSub[i] = Math.min(maxSub[i - 1]*nums[i],nums[i]);
            }
            
            global = Math.max(maxSub[i],global);
        }
        
        return global;
    }
}

【例】#### 分割回文串 II
这个题分成两步骤:(1)判断是否是回文
(2)统计回文长度
判断一串字符串中的某些字串是否是回文占用O(n2)的复杂度。进行回文的切割占用O(n1)的复杂度,同时还需要筛选出最小的切割数,所以这两步总共占用O(n2)的复杂度。
注意:在进行切割的时候只有在是回文的时候才会进行切割,所以需要首先对是否是回文进行判断。

class Solution {
    public int minCut(String s) {
        final int len = s.length();
        int[] count = new int[len + 1];
        //boolean[][] dp = new boolean[len][len];
        int min = len;
        
        for(int i = 0;i < len;i++){
            count[i] = i;
            for(int j = 0;j <= i;j++){
                if(s.charAt(i) == s.charAt(j) && (i-j <= 1 || dp[j + 1][i - 1])){
                    dp[j][i] = true;
                    if(j == 0){
                        count[i] = 0;
                    }else{
                        count[i] = Math.min(count[j - 1] + 1,count[i]);
                    }
                    //count[i] = Math.max(count[i],count[j+1] + 1);
                }
            }
        }
        
        return count[len-1];
    }
}

(3)区间DP
【例】爆裂气球

贪心算法

深度优先搜索

与记忆化递归的区别就是:
记忆化递归并不需要构成完成的搜索树,比如剑指 offer中的字符串构成(能否组成bfg)。按照记忆化递归的来解释,首先寻找b(及入口),然后按照深度优先搜索的方式进行搜索,如果没有找到(1. 一直访问到叶子节点 2. 下一个要找的点是f,但是出现了g,那么退回)则需要回退操作(回退包含,将访问节点从路径中删除,将访问标识回置)。

记忆化递归

【例】矩阵中的路径

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352