刷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 stack;
    private Stack 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 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 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

最后一个是没采用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遇到需要会员才能做的题了。好蓝瘦,不能白嫖了~

相交链表

题目:编写一个程序,找到两个单链表相交的起始节点。
刷leetCode算法题+解析(十三)_第1张图片
题目截图

刷leetCode算法题+解析(十三)_第2张图片
题目截图

刷leetCode算法题+解析(十三)_第3张图片
题目截图

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

刷leetCode算法题+解析(十三)_第4张图片
是时候展示我的画技了!

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

/**
 * 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 map = new HashMap();
        for(int i = 0;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]

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

你可能感兴趣的:(刷leetCode算法题+解析(十三))