刷leetCode算法题+解析(十三)

又是一个愉快的周五,继续刷题ing~

最小栈

题目:设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) -- 将元素 x 推入栈中。
pop() -- 删除栈顶的元素。
top() -- 获取栈顶元素。
getMin() -- 检索栈中的最小元素。

示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.

前言:这里先认个怂,这道题我思路不行。 我一直承认与非科班的短板还有工作以来急功近利的心态。至于这种很少见的数据结构(除了数组,集合,map之外的数据结构)链表和树都还是在LeetCode上学的。Queue队列是前一段时间树的遍历了解的。而这道题用到的栈。讲真我在这道题之前连单词都不会写。一开始我试图用自己的思路来解题,唯一这种类型先入后出的也就是队列。所以在尽量用Queue来解题,卡在了getMin这块。最终还是直接决定看解析了。现在不会,看了解析我会了不就行了么?还好网上那么多好心的大大出各种解析,解释和思路讲解。讲的很透彻和明白,尤其是有一个大大提供了多思路的讲解,每一种都让我豁然开朗,这里再次表达感谢。
思路:这才是真正的解题思路。简单说一下这道题很多种做法可以做。首先Java提供的栈Stack。它本身已经有了前三个方法的实现,也就是这道题如果用了这个Stack只要实现最后一个最小值就可以了。当然也可以不用这个Stack,但是这种思路后面说。先说用Stack实现最小值
首先一种最简单的办法:用空间换时间。这里只要求常熟时间内检索到最小元素的栈。并没有对空间有要求。我们完全可以使用两个栈,一个是正常的栈,另一个是专门记录最小值的栈。这样取最小值只需要一步,简单的多。直接上代码:

class MinStack {
   
    private Stack<Integer> stack;
    private Stack<Integer> min;
    /** initialize your data structure here. */
    public MinStack() {
        stack = new Stack();
        min = new Stack();
    }
    
    public void push(int x) {
        stack.push(x);
        //最小值这个栈不为空
        if(!min.isEmpty()){
//当新push的比最小栈中的首元素大就把这个push到最小栈中
            if(min.peek()>=x){
                min.push(x);
            }
        }else{
            min.push(x);
        }
    }
    
    public void pop() {
        //这里有个坑,不能直接比较,不然两个Integer得判断equals
        int x =stack.pop();
        if(min.peek()==x){
            min.pop();
        }
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        return min.peek();
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

其实这个是最容易理解的了,也很简单,但是大佬在这个基础上改良优化了一下。这个是很明显的用空间换时间。能不能只用一个栈就完成这个需求?真的非常佩服大佬的思路广而且闲(绝对是褒义词,喜欢钻研的意思)这一个简单 题不断优化,提供多种不同思路。咱们继续说这个题上一版的优化版。如果用一个栈,则需要一个常量来表示最小值。但是会出现一种状态,每次这个常量表示的都是最小值。但是这个最小值上一个最小值记录找不到了。如果这个最小值出栈了就gg了、所以重点是要保存最小值的同时保存第二小的值。这样不管怎么出栈都不会gg。
然后大佬的思路是在push最小值的时候,把之前最小值也压入栈。这样就算是最小值出栈了,我们还能知道第二小的(也就是现在的最小的)。说起来很复杂但是只要理解原理了还是很简单的。我这里直接上代码了。

class MinStack {
   
    private Stack<Integer> stack;
    //因为这个min取值万一push进来的都大于初始值就会出错。所以直接min赋值最大值。
    private int min = Integer.MAX_VALUE;
    /** initialize your data structure here. */
    public MinStack() {
        stack = new Stack();
    }  
    public void push(int x) {
        if(min>=x){
            //把之前最小的push进去。
            stack.push(min);
            //把min改成最小值
            min=x;   
        }
        stack.push(x);
    }   
    public void pop() {
        int x =stack.pop();
        //说明最小值被pop了,min应该等于第二小的。并且顺便把第二小的也pop出去。
        if(min==x){
            min = stack.pop();
        }
        
    }    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        return min;
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

再然后大神还提供了几个思路,一个是取巧的减法。这个就是保存的数据是每一个值和最小值的差。然后如果遇到比这个最小值还小的数字这个差就会是负数。同时更新最小值min的值。这样每次取出值的时候判断正负就好了。如果是负的,我们用最小值减去这个负数得到的就是第二小的值了。
其实想想也知道,这个方法运行起来跟上一个比不用往栈中插入额外的数据。但是每次取出值都要加以判断。具体哪个比较好还真不好说。而且因为保存差值,可能会溢出,所以要用long存,不能用int存。

class MinStack {  
    //涉及到运算,可能会溢出,所以这里用long
    private Stack<Long> stack;
    //因为这个min取值万一push进来的都大于初始值就会出错。所以直接min赋值最大值。
    private long min;
    /** initialize your data structure here. */
    public MinStack() {
        stack = new Stack();
    }  
    public void push(int x) {
        if(stack.isEmpty()){
            min = x;
            stack.push(x-min);
        }else{
            stack.push(x-min);
            if(x<min){
                //新插入的数据是负的。并且替换min值                
                min = x;
            }
            
        }
    }   
    public void pop() {
        //因为这个是不用有返回值的,所以直接弹出去就行了。管他数据对不对呢
        long x =stack.pop();
        //说明最小值被pop的是最小值。
        if(x<0){
            min = min-x;
        }
        
    }    
    public int top() {
        //如果是负数这个出栈的值是min
        if(stack.peek()<0){
            return (int)min;
        }else{
            return (int)(stack.peek()+min);
        }
    }
    
    public int getMin() {
        return (int)min;
    }   
}
/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

最后一个是没采用java的Stack自己链表实现的方法。这个其实道理不难,而且思路也简单。但是因为是自己建造底层数据结构所以代码多点。
原理就是链表,并且在每一个节点除了存储节点本身的值还存储最小值。所以每次获取最小值只要知道当前节点的最小值就可以了。 也是一种用空间换时间的做法。直接上代码:

class MinStack {  
    class Node{
        int node;
        int min;
        Node next;
        Node(int val,int min){
            this.node = val;
            this.min = min;
            next =null;
        }
    } 
    
    private Node node;
    /** initialize your data structure here. */
    public MinStack() {
    }  
    //这块区别于常用的链表,每次新push的节点要放在头部。所以是把node往后挂
    public void push(int x) {
        if(node==null){
            node = new Node(x,x);
        }else{
            Node n = new Node(x,Math.min(x,node.min)); 
            n.next = node;
            node = n;
        }
    }   
    public void pop() {
        //把头节点拿走。因为之前存的时候每一个节点都有当前数据的最小值,所以根本不用考虑最小值
        if(node!=null){
            node = node.next;
        }
    }    
    public int top() {
        return node.node;
    }
    
    public int getMin() {
        return node.min;
    }   
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

然后这道题终于结束了,整整用了半天,理大佬思路,一点点按照理解写代码。再一次次执行,最后写下来。虽然时间耽误挺多但是真的觉得学到了很多东西。顺便心得就是做题一定要踏下心来慢慢的分析琢磨,越着急思路越不清晰。下一题吧。
LeetCode遇到需要会员才能做的题了。好蓝瘦,不能白嫖了~

相交链表

题目:编写一个程序,找到两个单链表相交的起始节点。
题目截图

题目截图

题目截图

思考:反正如图所示吧,单纯的找到焦点还是很简单的,暴力双循环吧。但是真的太low,人家都说了要尽量满足O(n)时间复杂度和O(1)内存了。所以还是想想怎么取巧吧。
真遗憾,我没想出来满足要求的解题方法。因为我觉得这个题不取巧谁都能做,所以我也没暴力法写出来。直接看了解析。然后有一次被别人的思路所震惊。
思路:直接说方法吧。思路就是两个指针分别遍历链表A,B。A遍历完接着遍历B。B遍历完接着遍历A。这两个指针的交叉点就是链表的交点。可能我这么说很难理解,我这里用图表示

是时候展示我的画技了!

所以这个题的解就是两个链表互相加一下,然后一直遍历到,直到两个链表相等的节点就是相交点,如果遍历完了也没有相交点说明就是不相交的两个链表。
思路理解了代码很容易实现的:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null || headB==null){
            return null;
        }
        ListNode ha = headA;
        ListNode hb = headB; 
        //A+B只加一次,必须有个标,不然就循环追尾了
        boolean flag1 = true;
        boolean flag2 = true;       
        while(ha!=null){
              if(ha==hb){
                  return ha;
              }else{
                  if(ha.next == null && flag1){
                      //已经追加一次就要把标改状态。
                      ha = headB;
                      flag1 = false;
                  }else{
                      ha = ha.next;   
                  }
                  if(hb.next == null && flag2){
                       hb = headA;
                       flag2 = false;
                  }else{
                      hb = hb.next;
                  }                
              }
        }
        return null;
    }
}

然后我看到了另一种写法,思路是一样的,我这里是用flag判断只追加一次的。还有一种思路就是判断条件是A==B,不管是A+B或者B+A,在走完一遍的时候最终会归于null。所以那么判断也可以。只要理解就行。我刚刚也改了那种方法运行了一下,两个方式其实性能差不多。选择能理解的喜欢的就好。

两数之和二

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

思路:这道题我做过,我不知道他和两数之和1 有什么区别,除了下标是从1开始。反正做是第一时间做出来了,怎么优化还得慢慢来。先把第一版贴上来

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int [] result = new int[2];
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        for(int i = 0;i<numbers.length;i++){
            //有答案则直接返回。这两个数对找到了.这有个优化点,就是因为升序排列,而且不会有重复,所以说两个加数中较大的那个肯定比target/2大。因为有负数问题,所以是大于等于
            if(numbers[i]>=target/2 && map.containsKey(target-numbers[i])){
                result[0] = map.get(target-numbers[i]);
                result[1] = i+1;
                return result;
            }else{
                //没有把这个数和下标存进map。因为下标不是从0开始,所以存i+1。
                map.put(numbers[i],i+1);
            }     
        }
        return null;
    }
}

我觉得我已经绞尽脑汁的优化了,但是性能还是不行。其实能理解:因为可能是上面这种遍历的方法本身就有问题,所以我换个思路试试。
实在没思路忍不住看了题解,然后豁然开朗。真的,一看就会。一点不夸张。差的就是那个思维模式。哎,其实就是双指针法。最大最小相加跟给定的比较。大了右指针往左移。小了左指针往右移。知道正好找到那两个数为止。就这么简单粗暴。因为题目已经说了肯定有一对是可以的。所以我也没做判断。再说溢出问题。其实这个不用考虑。不然你拿点数据测试一下就行了。因为这个溢出后结果肯定是不对的。然后开始挪位。最后肯定是挪到不溢出的地方了。直接上代码吧

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int [] result = new int[2];
        int left= 0;
        int right =numbers.length-1;
        while(true){
            //这两个数相加比给定的大。则最大值往小
            if(numbers[left]+numbers[right]>target){
                right--;
            }else if(numbers[left]+numbers[right]<target){//这两个数相加比给定值小,则最小值往大
                left++;
            }else{
                result[0] = left+1;
                result[1] = right+1;
                return result;
            }
        }
    }
}

好了,今天的三道题就到这,其实学习的乐趣不在于学会了怎么怎么样,而在于懂了的那一刻的成就感和自豪感。我是正在试图入门的算法小白,每天进步一点点~
如果稍微帮到你了记得点个喜欢点个关注呦~也祝大家工作顺顺利利!周末愉快!

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

推荐阅读更多精彩内容

  • 注意:本文适用于已刷过题目,想短短几分钟快速简单回顾的情况。没看过《剑指offer》的读者建议先阅读下。 斐波那契...
    FeelsChaotic阅读 1,724评论 2 8
  • 做题,实际写出例子,然后分析可能遇到的情况,慢慢的,思路就会出来了。 线性表 33. Search in Rota...
    小碧小琳阅读 1,594评论 0 2
  • 目录 1. 栈和队列1.用两个队列实现栈2.用两个栈实现队列3.实现一个栈,可以用常数级时间找出栈中的最小值4.判...
    MigrationUK阅读 3,033评论 4 20
  • “区块链走过那么多年,可是大家依然用着中心化的交易平台交易去中心化的资产,这是很悲哀也很无奈的。” ...
    区块链学长说阅读 227评论 0 0
  • 所有上市公司发行的可转债的条款都大同小异,所以读懂了一家上市公司的可转债募集说明书,那么研究其他公司的可转债时仅需...
    漫谈低风险理财阅读 247评论 0 2