【刷题】LeetCode部分题型技巧

前言

这是用来记录在刷LeetCode时遇到的一些问题的技巧,因只记录一些技巧或优化方案故不一定全部记录,自认为的最佳方案已添加+前缀。另可通过搜索题号查看相关问题。


题表

1】. 两数相加
简要介绍:给定一个数组与一个目标数字,在该数组中寻找两个数,使其和等于目标,返回其下标数组
给定条件:题目必定有解,且每个数至多使用一次

  • 暴力遍历:两重循环,简单不解释。时间复杂度O(n^2),空间复杂度O(1)
  • 排序二分:第一遍排序,第二遍二分查找,实现复杂。时间复杂度O(nlogn),空间复杂度依赖于排序算法。
  • +哈希索引:借助哈希表,可一次遍历解析,实现简单。假设哈希效率高,单次查找为O(1),则时间复杂度可视为O(n),空间复杂度O(n)

28】. 实现经典的strstr()方法
简要介绍:给定base字符串以及need字符串,要求在前者中找到后者的第一次出现的下标
给定条件:题目不一定有解,找不到时返回-1

  • 暴力遍历:两重循环对比搜索,简单不解释。时间复杂度O(m*n),空间复杂度O(1)
  • 暴力遍历优化方案:两重循环,减少第一重循环的长度为base.length-need.length。时间复杂度O((m-n)*n),空间复杂度O(1)
  • +KMP算法:第一步(核心),线性遍历need字符串,得到前缀匹配数量数组next[],该数组每个位置的整数表示need字符串对应位的字符最长公共前缀的位数,换个角度思考呢,就是最长公共前缀的匹配下标;第二步,线性遍历base字符串,按暴力遍历进行匹配,如果未匹配上则根据need数组对base指针i进行跳转。时间复杂度O(m+n),空间复杂度O(n)
    代码直通车:
class KMPSolution {
    private int[] next(String needle) {
        if (needle.length() <= 0) {
            throw new RuntimeException("KMP does not support empty string.");
        }
        int next[] = new int[needle.length() + 1];
        next[0] = -1;
        for (int i = 0, j = -1; i < needle.length(); ) {
            if (j == -1 || needle.charAt(i) == needle.charAt(j)) {
                ++i;
                ++j;
                next[i] = j;
            } else {
                j = next[j];
            }
        }
        return next;
    }

    public int strStr(String haystack, String needle) {
        if (needle.length() == 0) return 0;
        int[] next = next(needle);
        int i = 0, j = 0;
        while (i < haystack.length() && j < needle.length()) {
            if (j == -1 || haystack.charAt(i) == needle.charAt(j)) {
                ++i;
                ++j;
            } else {
                j = next[j];
            }
        }

        if (j == needle.length()) {
            return i - j;
        }
        return -1;
    }
}
  • ++积累哈希【线性空间优化完毕】:我们以ababacadc为例,在该串中找aca,那么将被寻找串分为多个三个字母的子串也就是aba, bab...对每个串进行哈希,并将哈希值与寻找串进行对比,若相等则进行匹配。
    那么问题来了,这里的关键是如何取寻找一个o(1)的哈希算法,来对子串求哈希,按理说如果你需要去对每个子串求其哈希,必定需要考虑每个字符所以至少复杂度应该为o(n)。想到这里,本应该是难以进行下去了的。但考虑一下子串的序列构成,每个相邻的子串其实只相差第一个与最后一个字符,不妨尝试预求值+积累序列的方式:对第一个子串求字母和,然后后面每个新串的处理方案为减去第一个字母加上最后一个字母。来了,o(1)的哈希算法。
    后面的详细方案就不需要说了,但是这个算法有个问题就是对于边界情况效率可能较低,如和哈希大量重复,如子串长度几乎等于母串长度。至少这个方案如果不特意为难,应该是最佳的方案。
    时间复杂度o(m),空间复杂度o(1)【进行了线性空间优化】
    代码直通车:
class HashSolution {
    public int strStr(String haystack, String needle) {
        if(needle.length() == 0) return 0;
        if(haystack.length() < needle.length()) return -1;
        int needleHash = 0, hayHash = 0;
        int nl = needle.length(), hl = haystack.length(), del = hl - nl;
        for(int i = 0 ; i < nl ; ++i){
            needleHash += needle.charAt(i);
            hayHash += haystack.charAt(i);
        }
        for(int i = 0 ; i <= del ; ++i){
            if(i != 0){
                hayHash -= haystack.charAt(i - 1);
                hayHash += haystack.charAt(i + nl - 1);
            }
            if(hayHash == needleHash){
                int j = 0;
                for(; j < nl ; ++j){
                    if(haystack.charAt(i+j) != needle.charAt(j)){
                        break;
                    }
                }
                if(j == nl){
                    return i;
                }
            }
        }
        return -1;
    }
}

53】. 最大子数组
简要介绍:给定一个整型数组,要求寻找到和最大的连续子数组,并返回其最大和

  • 线性DP:记给定数组为n,建立与该数组长度一致的数组m,记录以对应位置的整型数为终点的最大和,得到递归方程m[i]=max{m[i-1]+n[i], n[i]},并维持返回值max=max{m[i], max}即可。时间复杂度O(n),空间复杂度O(n)
  • +线性DP优化:发现递归方程与m[0...i-2]无关,故可优化空间复杂度,仅记录当前位的最大值为cur,修改递归方程为cur=max{cur+n[i], n[i]},并维持返回值max=max{cur, max}。时间复杂度O(n),空间复杂度O(1)

69】. 整数平方根
简要介绍:求一个整数的平方根,小数部分截取

  • 递增遍历:一重递增循环,简单不解释。时间复杂度O(val(i))
  • +二分查找:左为0右为i二分查找,考虑的要多一些。时间复杂度O(log(val(i))),空间复杂度O(1)。(LeetCode上时间效率已经100%了)
    以下为蛇皮鬼神估算解决方法
  • ++牛顿迭代法:对于f(x)=x^2-a,出于其导数f'(x)=2x,可利用切线切于(m, f(m))时与x轴的交点t更逼近于a,由等式2m(m-t)=m^2-a得解t=(m^2+a)/(2m),对t进行迭代得t=(t^2+a)/(2t)。于是在给定精确度下,给出以下代码(emm,出于一些原因,在高精度上有一定的问题,无法通过LeetCode):
public int mySqrt(float x)
{
    final float precision = 0.1f;
    float res = x, pre;
    do
    {
        pre = res;
        res = (res + x/res) / 2;
    }while(Math.abs(res-pre) > precision);
    return (int)res;
}
  • +++++Quake-III鬼数迭代法:神级鬼数迭代计算平方根的方法,反正俺看不懂,且因为同时运用了指针,故只能贴C++代码。带个鬼数,少量运算高精度结果,大神的想法我也只能是贴贴而已。
float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y   = number;
    i   = * ( long * ) &y;   // evil floating point bit level hacking
    i   = 0x5f3759df - ( i >> 1 ); // what the fuck?
    y   = * ( float * ) &i;
    y   = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
    // y   = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

    return y;
}  

70】. 爬楼梯
简要介绍:给定一个数字表示有几阶楼梯,并一次只能爬1或者2阶楼梯,求有几种爬法。

  • 线性DP:假定需要爬第i阶阶梯,则其爬法等于爬i-1阶的方法数量加上爬i-2阶的方法数量,递归方程m[i]=1,i=0 & m[i]=1,i=1 & m[i]=m[i-1]+m[i-2], i>=2。时间复杂度O(n),空间复杂度O(n)
  • +线性DP优化:同题【53】一样的DP优化方法,进行空间优化,不赘述。

136】. 出现一次的数字
简要介绍:给定一个数组,要求找出其中只出现一次的数字
给定条件:其他的出现的数字必定出现两次,出现一次的数字只有一个

  • 暴力遍历:简单不优雅不说,也懒得分析。
  • 哈希索引:一遍循环,第一次出现插入哈希表,第二次出现移出哈希表,最后找到该哈希表中唯一的数字。假设哈希效率高,单次查找为O(1),则时间复杂度可视为O(n),空间复杂度O(n)
  • 集合运算:第一遍循环,将数组中数字添加进入集合,并同时计算总和sum1;第二遍对集合循环,求其和sum2;出现一次的数字值=2*sum2-sum1,时间复杂度确定性的O(n),空间复杂度O(n)
  • +异或运算:基础公式a^b^b=a,一边循环,初值0对每个数字进行异或,最后该值即为只出现一次的值。

141】. 链表环判定
简要介绍:给定一个链表,判断其是否有环

  • 暴力遍历:暴暴暴暴力,别了吧。。。
  • 哈希索引:一遍循环,每次到一个节点,先检查是否在哈希表里,如果不在,则添加进去;如果在,则说明遇环;遍历至尾说明无环。时间复杂度O(n),空间复杂度O(n)
  • +快慢指针:一遍循环,一个指针一次走一格,一个指针一次走两格,若两个指针发生相遇说明存在环;若两个指针不发生相遇且快指针到尾,说明不存在环。

142】. 链表环节点寻找
简要介绍:给定一个链表,判断其是否有环并找出环节点
改进自:【141

  • 哈希索引:一遍循环,每次到一个节点,先检查是否在哈希表里,如果不在,则添加进去;如果在,则说明遇环,返回该节点即可;遍历至尾说明无环。时间复杂度O(n),空间复杂度O(n)
  • +快慢指针: 一遍循环,一个指针一次走一格,一个指针一次走两格,若两个指针发生相遇说明存在环。以下分析基于有环,沿链表方向将链表分为三个部分:第一段为头到环点记长度为a;第二段为环点到相遇点记长度为b;第三段为相遇点到环点记长度为c,并记环长度为r,有等式r=b+c
    1. s=a+b & 2s=s+n*r
    2. =>s=a+b & s=n*r
    3. =>a=n*r-b
    4. =>a=(n-1)*r+c
    5. =>a-c=(n-1)*r
      这个结果意味着什么呢,也就是从起始点到环点的距离之差等于相遇点到环点的距离之差。则给出以下算法:
      快慢指针相遇时,同时从起点与相遇点发出两个慢指针,该两个慢指针第一次相遇的点即为环点。
      时间复杂度O(n),空间复杂度O(n)

155】. 最小栈
简要介绍:设计一个栈,其提供一个方法,能获取当前栈中的最小值

  • 遍历栈:普通设计该栈,当调用getMin()时,遍历栈空间取最小值。时间复杂度O(n),空间复杂度O(1)
  • 辅助空间:普通设计该栈,同时建立辅助栈储存最小值,每次压栈比较其顶端数据与压栈数据,压入最小值即可。时间复杂度O(1),空间复杂度O(n)

160】. 寻找两个链表第一个交点
简要介绍:给定两条链表,寻找这两条链表的第一个交点,如果不相交则返回null

  • 暴力遍历:链表双重循环,比较引用是否相等。时间复杂度O(n^m),空间复杂度O(1)
  • 辅助栈:对两个链表建立辅助栈,遍历时将遍历到的节点插入栈中,当遍历结束后开始出栈,寻找到第一个不相等的节点则前一个出栈节点为相交位置。时间复杂度O(m+n),空间复杂度O(m+n)
  • 哈希索引:第一次用很新鲜感觉很棒,然后就熟了。
  • +计算长度:第一次分别遍历,对两个链表计算长度;第二次一起遍历,让长的链表先走长度差值,然后开始比较引用是否相等,第一个相等的节点为相交点。时间复杂度O(m+n),空间复杂度O(1)

169】. 最常出现的数
简要介绍:给定一个数组,要求在里面找到出现次数大于数组一半大小的数

  • 暴力遍历:双重循环,无意义。时间复杂度O(n^2),空间复杂度O(1)
  • 哈希索引key存值,value存出现次数,每次迭代查看value值是否大于n/2。依然是假设哈希效率高,时间复杂度O(n),空间复杂度O(n)
  • 排序中分:将数组进行排序,因为这个数字出现的次数大于数组一半大小,所以数组中位数必定是这个数字。时间复杂度与空间复杂度依赖于排序算法。
  • +投票算法:描述如下,选定一个候选人,每当有人投他则加票,每当有人投其他人则减票,票数为0时根据下一张票来确定新的候选人。因为该数字出现的次数大于数组一般大小,一遍循环下来必定为该数字。时间复杂度O(n),空间复杂度O(1)

203】. 删除链表中的数
简要介绍:给定一个链表以及一个目标数字,要求在里面删除所有值等同于该目标数字的链表节点

  • 三行代码:【哈哈哈只是为了记录这三行代码】
  public ListNode removeElements(ListNode head, int val) {
        ListNode node = head, pre = head;
        while (node != null)  node = val == node.val? pre == node ? (head = pre = node.next) : (pre.next = node.next):(pre = node).next;
        return head;
    }

解题与优化方案总结

#】哈希表:将已经遇到过的对象存入哈希表,可进行假设o(1)的时间消耗

#】双指针:诸如链表环判定,倒数第K个节点balabala

#】线性空间优化:在题目所需要的额外空间是线性空间时可进行空间优化

  • 应用条件:依次进行下标增加的线性位置的值计算,且该值计算不依赖于整体的线性空间,比如计算下标为i时值时,仅需要前一个或两个等固定数目的值,且最后仅需返回最后一个线性位置的值
  • 利用变量存储所需要数目的值,在更新目标值后更新变量即可。
  • 扩展:多维空间优化,如题【72】编辑距离,该题一般情况下,空间消耗应为o(n^2),但实际上分析发现,下标为(i,j)的值计算仅依赖于三个位置的值分别是(i-1,j-1), (i-1,j), (i,j-1),便可同样使用与该优化方案一样的思想来进行空间优化,可达到o(n),更高维的空间优化仍然可以根据一样的思想进行优化。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342