左神算法初级班三 笔记

手写栈:

public class Stack {
    int top=0;
    int[] data;

    public Stack(int initsize){
        if(initsize<0)throw new IllegalArgumentException("Initsize should not less than 0!");
        data=new int[initsize];
        top=0;
    }

    public int peek(){//获取栈顶元素但不弹出
        if(top==0)throw new ArrayIndexOutOfBoundsException("Stack is empty!");
        return data[top-1];
    }

    public int pop(){//获取栈顶元素并弹出
        if(top==0)throw new ArrayIndexOutOfBoundsException("Stack is empty!");
        return data[--top];
    }

    public void push(int x){
        if(top==data.length)throw new ArrayIndexOutOfBoundsException("Stack is full!");
        data[top++]=x;
    }

    public boolean isEmpty(){
        return top==0;
    }

    public static void main(String[] args) {
        Stack stack=new Stack(10);
        for(int i=1;i<=5;++i)stack.push(i);
        while(!stack.isEmpty()){
            System.out.println(stack.pop());
        }
    }

}

手写队列:一般教材里使用start+end+一个空位置来实现循环队列,但是边界扣起来复杂,可以引入一个size表示已有元素个数,通过size来约束插入和删除行为。

public class Queue {
    int start,end,size;
    int[] data;

    Queue(int initsize){
        if(initsize<0)throw new IllegalArgumentException("Init size should be more than 0");
        size=start=end=0;
        data=new int[initsize];
    }

    public int peek(){
        if(size==0)throw new ArrayIndexOutOfBoundsException("The queue is empty!");
        return data[start];
    }

    public void push(int x){
        if(size==data.length)throw new ArrayIndexOutOfBoundsException("The queue is full!");
        size++;
        data[end]=x;
        end=(end+1)%data.length;
    }

    public int poll(){
        if(size==0)throw new ArrayIndexOutOfBoundsException("The queue is empty!");
        size--;
        int temp=start;
        start=(start+1)%data.length;
        return data[temp];
    }

    public boolean isEmpty(){
        return size==0;
    }

    public static void main(String[] args) {
        Queue queue=new Queue(10);
        for(int i=1;i<=5;++i)queue.push(i);
        while(!queue.isEmpty()){
            System.out.println(queue.poll());
        }
    }
}

面试题:设计一个栈,可以返回当前栈内的最小值。

思路:增加一个min栈,用于维护当前栈顶到栈底的最小值。

import java.util.*;
public class minStack {

    private java.util.Stack<Integer> Data;
    private java.util.Stack<Integer> Min;

    public int peek(){
        if(Data.size()<=0)throw new ArrayIndexOutOfBoundsException("The Stack is empty!");
        return Data.peek();
    }

    public int pop(){
        if(Data.size()<=0)throw new ArrayIndexOutOfBoundsException("The Stack is empty!");
        return Data.pop();
    }

    public int getMin(){
        if(Min.size()<=0)throw new ArrayIndexOutOfBoundsException("The Stack is empty!");
        return Min.peek();
    }

    public boolean isEmpty(){
        return Data.isEmpty();
    }

    public void push(int x){
        if(Min.isEmpty()){
            Min.push(x);
        }else if(x<Min.peek()){
            Min.push(x);
        }else{
            int tmp=Min.peek();
            Min.push(tmp);
        }
        Data.push(x);
    }

}

使用栈结构实现队列:(使用栈结构完成BFS)

栈每次需要弹出最后进的元素,而队列中后进的元素在尾部,所以可以先把除尾部外的元素倒出,再取出队尾即可。

import java.util.Queue;

public class QueueToStack {

    private Queue<Integer> Data;//始终保持数据放在Data里
    private Queue<Integer> Help;

    public int peek(){
        if(Data.isEmpty())throw new RuntimeException("Stack is empty!");
        while(Data.size()>1)Help.add(Data.poll());
        int ans=Data.poll();
        Help.add(ans);
        swapQueue();
        return ans;
    }

    public int pop(){
        if(Data.isEmpty())throw new RuntimeException("Stack is empty!");
        while(Data.size()>1)Help.add(Data.poll());
        int ans=Data.poll();
        swapQueue();
        return ans;
    }

    public void push(int x){
        Data.add(x);
    }

    public void swapQueue(){
        Queue<Integer> que=Data;
        Data=Help;
        Help=que;
    }
    
    public boolean isEmpty(){
        return Data.isEmpty();
    }

}

使用栈结构实现队列(使用队列结构完成DFS):

使用两个栈,使用辅助栈将被逆序的数据再度逆序回来。需要遵循的原则是:

1.每次借助辅助栈逆序的时候,必须保证辅助栈内没有元素

2.每次使用辅助栈逆序的时候,必须把数据栈的数据全部给辅助栈

import java.util.Date;

public class StackToQueue {

    private java.util.Stack<Integer> Data;//压入数据只发生在Data栈
    private java.util.Stack<Integer> Help;

    public void push(int x){
        Data.push(x);
    }

    public int poll(){
        if(Help.isEmpty()&&Data.isEmpty())throw new RuntimeException("Queue is empty!");
        reverseData();
        return Help.pop();
    }

    public int peek(){
        if(Help.isEmpty()&&Data.isEmpty())throw new RuntimeException("Queue is empty!");
        reverseData();
        return Help.peek();
    }

    public void reverseData(){
        if(Help.isEmpty()){
            while (!Data.isEmpty()){
                Help.push(Data.pop());
            }
        }
    }
    
    public boolean isEmpty(){
        return Data.isEmpty()&&Help.isEmpty();
    }

}

工程应用:猫狗队列

宠物、狗和猫的类如下: public class Pet { private String type; public Pet(String type) { this.type = type; } public String getPetType() { return this.type; } } public class Dog extends Pet { public Dog() { super("dog"); } } public class Cat extends Pet { public Cat() { super("cat"); } }
实现一种狗猫队列的结构,要求如下: 用户可以调用add方法将cat类或dog类的实例放入队列中; 用户可以调用pollAll方法,将队列中所有的实例按照进队列的先后顺序依次弹出;用户可以调用pollDog方法,将队列中dog类的实例按照进队列的先后顺序依次弹出; 用户可以调用pollCat方法,将队列中cat类的实例按照进队列的先后顺序依次弹出; 用户可以调用isEmpty方法,检查队列中是否还有dog或cat的实例; 用户可以调用isDogEmpty方法,检查队列中是否有dog 类的实例; 用户可以调用isCatEmpty方法,检查队列中是否有cat类的实例。

思路:首先我们还是为猫和狗各自设计一个队列,然后当我们通过为每一个类增加一个进入队列的时间戳来完成pollAll方法。

import java.util.Queue;

class Pet {
    private String type;
    public Pet(String type) { this.type = type; }
    public String getPetType() { return this.type; }
}

class Dog extends Pet {
    public Dog() {
        super("dog");
    }
}

class Cat extends Pet {
    public Cat() {
        super("cat");
    }
}

class PetEnter{
    private Pet pet;
    private int time;

    public PetEnter(Pet pet, int time) {
        this.pet = pet;
        this.time = time;
    }

    public Pet getPet() {
        return pet;
    }

    public void setPet(Pet pet) {
        this.pet = pet;
    }

    public int getTime() {
        return time;
    }

    public void setTime(int time) {
        this.time = time;
    }
}

class CatDogQue{

    private Queue<PetEnter> dogQue;
    private Queue<PetEnter> catQue;

    public boolean isDogEmpty(){return dogQue.isEmpty();}

    public boolean isCatEmpty(){return catQue.isEmpty();}

    public Dog pollDog(){
        if(dogQue.isEmpty())throw new RuntimeException("The dog que is empty!");
        return (Dog) dogQue.poll().getPet();
    }

    public Cat pollCat(){
        if(catQue.isEmpty())throw new RuntimeException("The cat que is empty!");
        return (Cat) catQue.poll().getPet();
    }

    public Pet pollAll(){
        if(catQue.isEmpty()&&dogQue.isEmpty())throw new RuntimeException("The cat que and dog que is empty!");
        if(catQue.isEmpty())return dogQue.poll().getPet();
        if(dogQue.isEmpty())return catQue.poll().getPet();
        return catQue.peek().getTime()>dogQue.peek().getTime()?dogQue.poll().getPet():catQue.poll().getPet();
    }
    
}

转圈打印矩阵

【题目】 给定一个整型矩阵matrix,请按照转圈的方式打印它。

例如:

1   2   3   4

5   6   7   8

9  10  11  12

13 14  15  16

打印结果为:1,2,3,4,8,12,16,15,14,13,9, 5,6,7,11, 10

【要求】 额外空间复杂度为O(1)。

思路:分解问题,每次我们打印以(lx,ly)为左上角,(rx,ry)为右下角的框,然后(lx++,ly++)、(rx--,ry--)。

public class PrintMartix {

    public static void printMaxtix(int[][] Arr){
        int lx=0,ly=0,rx=Arr.length-1,ry=Arr[0].length-1;
        while(lx<=rx&&ly<=ry){
            printRec(Arr,lx++,ly++,rx--,ry--);
        }
    }

    public static void printRec(int[][] Arr, int lx, int ly, int rx, int ry){
        int i;
        for(i=ly;i<ry;++i) System.out.print(Arr[lx][i]+",");
        for(i=lx;i<rx;++i) System.out.print(Arr[i][ry]+",");
        for(i=ry;i>ly;--i) System.out.print(Arr[rx][i]+",");
        for(i=rx;i>lx;--i) System.out.print(Arr[i][lx]+",");
    }

    public static void main(String[] args) {
        int[][] arr=new int[][]{{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
        printMaxtix(arr);
    }
}

旋转正方形矩阵

【题目】 给定一个整型正方形矩阵matrix,请把该矩阵调整成 顺时针旋转90度的样子。

【要求】 额外空间复杂度为O(1)。

还是按照框结构对原矩阵进行分解。此时我们只需要考察如何将框旋转即可。我们注意到,对于一个框来说,一次旋转中,每一个小组的四个元素之间发生了顺时针交换,如下图:

上图中,交换的四个元素坐标为(lx,ly+1) (lx+1,ry) (rx,ry-1) (rx-1,ly)。

public class Rotate {

    public static void rotateSquare(int[][] arr){
        int lx=0,ly=0,rx=arr.length-1,ry=arr[0].length-1;
        while(lx<=rx&&ly<=ry){
            rotateBorder(arr,lx++,ly++,rx--,ry--);
        }
    }

    public static void rotateBorder(int[][] arr,int lx,int ly,int rx,int ry){
        int up=rx-lx,tmp;
        for(int i=0;i<up;++i){
            tmp=arr[lx][ly+i];
            arr[lx][ly+i]=arr[rx-i][ly];
            arr[rx-i][ly]=arr[rx][ry-i];
            arr[rx][ry-i]=arr[lx+i][ry];
            arr[lx+i][ry]=tmp;
        }
    }

    public static void main(String[] args) {
        int[][] Arr=new int[][]{{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
        rotateSquare(Arr);
        for (int i = 0; i < Arr.length; i++) {
            for (int j = 0; j < Arr[i].length ; j++) {
                System.out.print(Arr[i][j]+"    ");
            }
            System.out.println();
        }
    }

}

但素嘞,还有别的办法。我们注意到,对原矩阵顺时针旋转90,其实效果上就是把第一行当作最后一列,第二行当作倒数第二列。。。。。。如果本题不要求矩阵元素实际改变,我们可以直接按照列读取后输出;如果要求改变,也可以申请辅助数组,读取后写回。但是不管怎么说,第一种分解问题的思路还是很重要的!

    public static void main(String[] args) {
        int[][] Arr=new int[][]{{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
        for(int j = 0; j < Arr[0].length ; ++j){
            for (int i = Arr.length - 1; i >= 0; i--) {
                System.out.print(Arr[i][j]+"   ");
            }
            System.out.println();
        }
    }

反转单向和双向链表

【题目】 分别实现反转单向链表和反转双向链表的函数。

【要求】 如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)

如果没有额外空间复杂度的要求,直接栈+链表重建即可。在O(1)额外空间复杂度的限制下,我们可以使用双指针pre和next,改变链表的指向,就可以完成链表的翻转。

public class ReverseList {

    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }

    public static Node reverseList(Node head){
        Node pre=null,next=null;
        while(head!=null){
            next=head.next;
            head.next=pre;
            pre=head;
            head=next;
        }
        return pre;
    }

    public static void printList(Node head){
        System.out.println("List:");
        while(head!=null){
            System.out.print(head.value+" ");
            head=head.next;
        }
        System.out.println();
    }

    public static class DoubleNode {
        public int value;
        public DoubleNode last;//前一个 后一个
        public DoubleNode next;

        public DoubleNode(int data) {
            this.value = data;
        }
    }

    public static DoubleNode reverseList(DoubleNode head){
        DoubleNode pre=null,next=null;
        while(head!=null){
            next=head.next;
            head.next=pre;
            head.last=next;
            pre=head;
            head=next;
        }
        return pre;
    }

    public static void printDoubleList(DoubleNode head){
        System.out.println("Double List(left to right)");
        DoubleNode end=null;
        while(head!=null){
            System.out.print(head.value+" ");
            end=head;
            head=head.next;
        }
        System.out.println("\nDouble List(right to left)");
        while(end!=null){
            System.out.print(end.value+" ");
            end=end.last;
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Node head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        printList(head1);
        head1 = reverseList(head1);
        printList(head1);

        DoubleNode head2 = new DoubleNode(1);
        head2.next = new DoubleNode(2);
        head2.next.last = head2;
        head2.next.next = new DoubleNode(3);
        head2.next.next.last = head2.next;
        head2.next.next.next = new DoubleNode(4);
        head2.next.next.next.last = head2.next.next;
        printDoubleList(head2);
        printDoubleList(reverseList(head2));

    }
}

“之”字形打印矩阵

【题目】 给定一个矩阵matrix,按照“之”字形的方式打印这 个矩阵,例如:

1   2   3   4

5   6   7   8

9  10  11  12

“之”字形打印的结果为:1,2,5,9,6,3,4,7,10,11, 8,12

【要求】 额外空间复杂度为O(1)。

 我们注意到,其实所谓的之字形就是按照斜线+拐弯的方式遍历数组。从而我们可以按照斜线将数组分解,只需要用一个变量辅助打印方向即可。设置两个点用于标注斜线的端点,其中,上部的端点在到达最右边之后只能向下,下部的端点在到达最下边之后只能向右。于是这样走下去,由于矩阵是矩形的,所以最后两个端点肯定同时落在矩阵右下角的点上。【因为每个点需要经历的点个数都是矩阵的长+宽】

public static void printLine(int[][] Arr,int Lx,int Ly,int Rx,int Ry,boolean dir){
        if(dir){//左下往右上
            while(Lx!=Rx-1){
                System.out.print(Arr[Lx--][Ly++]+" ");
            }
        }else{//右上往左下
            while(Rx!=Lx+1){
                System.out.print(Arr[Rx++][Ry--]+" ");
            }
        }
    }

    public static void printMartixWithZ(int[][] Arr){
        boolean dir=true;//左下向右上
        int Rx=0,Ry=0,Lx=0,Ly=0,row=Arr.length-1,col=Arr[0].length-1;
        while(Ly!=col+1){
            printLine(Arr,Lx,Ly,Rx,Ry,dir);
            Ly=Lx==row?Ly+1:Ly;
            Lx=Lx==row?Lx:Lx+1;
            Rx=Ry==col?Rx+1:Rx;
            Ry=Ry==col?Ry:Ry+1;
            dir=!dir;
        }
    }

    public static void main(String[] args) {
        int[][] arr=new int[][]{{1,2,3,4},{5,6,7,8},{9,10,11,12}};
        printMartixWithZ(arr);
    }

注意坑点:要保证Lx在Ly后更新,Ry在Rx后更新,主要是因为Ly的更新方式依赖于当前Lx的值,Rx的更新依赖Ry的当前值!

在行列都排好序的矩阵中找数

【题目】 给定一个有N*M的整型矩阵matrix和一个整数K, matrix的每一行和每一 列都是排好序的。实现一个函数,判断K 是否在matrix中。 例如:

0   1   2   5

2   3   4   7

4   4   4   8

5   7   7   9

如果K为7,返回true;如果K为6,返 回false。 【要求】 时间复杂度为O(N+M),额外空间复杂度为O(1)。

思路:我们可以从右上角开始,如果需要查找的值>当前位置的值,根据数据的性质,应该向下找,如果小于则向左,如果等于则查找成功。设矩形长宽分别为m、n,则最多查询m+n次。当然也可以从左下角开始。不从左上或者右下开始的原因是,如果目标大于当前值,那么我们无从知道下一步应该去右边还是下边,右下角同理。所以算法的复杂度为O(n+m)。

    public static int[] searchIndexInMartix(int[][] Arr,int target){
        int m=Arr.length-1,n=Arr[0].length-1,x=0,y=n;
        while(x<=m&&y>=0){
            if(Arr[x][y]==target)return new int[]{x,y};
            else if(Arr[x][y]>target)--y;
            else ++x;
        }
        return new int[]{-1,-1};
    }

    public static void main(String[] args) {
        int[][] arr=new int[][]{{0,1,2,5},{2,3,4,7},{4,4,4,8},{5,7,7,9}};
        int[] ints = searchIndexInMartix(arr, 3);
        System.out.println("x:" + ints[0] + " y:" + ints[1]);
    }

优化:其实在有序的结构上进行查找某个值,很容易想到二分。每次我们只需要在行和列中找到第一个不小于目标值的位置即可。同时还可以把行和列交换后再存储一份,这样在行和列上都可以二分了。当然也可以直接手写两种二分的方式,只不过在列上二分的时候,每个元素的索引间隔为一行。可以优化到O((logm+logn)^2)。【次数*每一次的代价估计】

    public static int binarySearchInCol(int[][] Arr,int target,int col,int L,int R){//在同一列上做二分 找到>=target的最小的数即让坐标靠上(尽可能小)
        int Mid,ans=-1;
        while(L<=R){
            Mid=(L+R)>>1;
            if(Arr[Mid][col]>=target){
                ans=Mid;
                R=Mid-1;
            }
            else{
                L=Mid+1;
            }
        }
        return ans;
    }

    public static int binarySearchInRow(int[][] Arr,int target,int row,int L,int R){//在一行中找 找到<=target的最大的数即让坐标靠右(尽可能大)
        int Mid,ans=-1;
        while(L<=R){
            Mid=(L+R)>>1;
            if(Arr[row][Mid]<=target){
                ans=Mid;
                L=Mid+1;
            }
            else{
                R=Mid-1;
            }
        }
        return ans;
    }

    public static int[] binarySearchIndexInMartix(int[][] Arr,int target){
        int m=Arr.length-1,n=Arr[0].length-1,x=0,y=n;
        while(x<=m&&y>=0){
            if(Arr[x][y]==target)return new int[]{x,y};
            else if(Arr[x][y]>target)y=binarySearchInRow(Arr,target,x,0,y);//在行上找
            else x=binarySearchInCol(Arr,target,y,x,Arr.length-1);//在列上找
            if(y==-1||x==-1)break;
        }
        return new int[]{-1,-1};
    }

    public static void main(String[] args) {
        int[][] arr=new int[][]{{0,1,2,5},{2,3,4,7},{4,4,4,8},{5,7,7,9}};
        int[] ints = binarySearchIndexInMartix(arr, 3);
        System.out.println("x:" + ints[0] + " y:" + ints[1]);
    }

打印两个有序链表的公共部分

【题目】 给定两个有序链表的头指针head1和head2,打印两个链表的公共部分。

思路:

1.使用哈希表,将表1的所有节点存入,遇到地址重复的直接打印

2.如果两个链表有公共部分,那么尾节点必相同,因为节点只有一个next指针,不可能在先有交集,然后在某个节点处岔开。我们可以遍历一遍,拿到链表长度,如果尾节点相同,则相交,此使可以让长链表指针先移动比短链表多出的部分,然后再同时移动,这样两个指针必定同时进入公共区域。

public class CommonList {

    static class Node{
        int val;
        Node next=null;

        public Node(int val) {
            this.val = val;
        }
    }

    public static void printCommonPart(Node p1, Node p2){
        Node end1=p1,end2=p2,tmp;
        int n=0;
        while(end1!=null){
            ++n;
            end1=end1.next;
        }
        while(end2!=null){
            --n;
            end2=end2.next;
        }
        if(end1!=end2){
            System.out.println("No common part!");
            return;//必不相交
        }
        //保证p1指向的链表长一些
        if(n<0){//链表二长
            n=-n;
            tmp=p1;
            p1=p2;
            p2=tmp;
        }
        while(n>0){
            p1=p1.next;
            --n;
        }
        while(p1!=p2){
            p1=p1.next;
            p2=p2.next;
        }
        while(p1!=null){
            System.out.print(p1.val+" ");
            p1=p1.next;
        }
    }

    public static void main(String[] args) {
        Node n1=new Node(1);
        Node n2=new Node(2);
        Node n3=new Node(3);
        Node n4=new Node(4);
        Node n5=new Node(5);

        Node h1=new Node(1);
        Node h2=new Node(2);

        n1.next=n2;
        n2.next=n3;
        n3.next=n4;
        n4.next=n5;
        h1.next=h2;
        h2.next=n4;

        printCommonPart(n1,h1);
    }
}

判断一个链表是否为回文结构

【题目】 给定一个链表的头节点head,请判断该链表是否为回文结构【是值相同,13->1->13符合题意】。 例如: 1->2->1,返回true。 1->2->2->1,返回true。 15->6->15,返回true。 1->2->3,返回false。
进阶: 如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1)。

思路:在不考虑空间要求的条件下,对于回文问题,我们一般使用栈,这需要O(n)的复杂度,并两遍遍历链表。我们可以使用快慢指针,让慢指针遍历过的点进栈,通过快慢指针拿到链表中点,然后继续遍历弹栈,可以省去一半的空间,也只需要遍历一遍链表。

    public static boolean ispalindrome(Node head){
        if(head==null)return true;
        Node slower=head,faster=head.next;
        Stack<Integer> stack=new Stack<Integer>();
        while(faster!=null&&faster.next!=null){
            stack.push(slower.val);
            slower=slower.next;
            faster=faster.next.next;
        }
        if(faster!=null)stack.push(slower.val);//链表长度为偶数
        while(slower.next!=null){
            slower=slower.next;
            if(stack.peek()==slower.val)stack.pop();
            else return false;
        }
        return true;
    }

    public static void main(String[] args) {
        Node n1=new Node(1);
        Node n2=new Node(2);
        Node n3=new Node(3);
        Node n4=new Node(2);
        Node n5=new Node(1);
        n1.next=n2;
        n2.next=n3;
        n3.next=n4;
        //n2.next=n4;
        n4.next=n5;

        System.out.println(ispalindrome(n1));
}

现在来思考如何使用O(1)的空间完成这个过程,首先快慢指针可以帮助我们拿到链表中点处的节点,然后我们可以可以借助pre和next将右边指向颠倒,并将中点指向null,然后从左右同时向中间跑,比对值,直到其中一个指针为null就停止。

    public static boolean ispalindrome2(Node head){
        if(head==null)return true;
        Node slower=head,faster=head.next,pre,next,tmp;
        Stack<Integer> stack=new Stack<Integer>();
        while(faster!=null&&faster.next!=null){
            stack.push(slower.val);
            slower=slower.next;
            faster=faster.next.next;
        }
        if(faster!=null)stack.push(slower.val);//链表长度为偶数
        //此使如果链表长度为偶数 则slower指向的是中间靠前的一个节点 否则slower指向中点
        pre=slower;
        next=slower.next;
        slower.next=null;
        while(next!=null){
            tmp=next.next;
            next.next=pre;
            pre=next;
            next=tmp;
        }
        //开始比对是否回文
        Node p1=head,p2=pre;
        while(p1!=null&&p2!=null){
            if(p1.val!=p2.val)return false;
            p1=p1.next;
            p2=p2.next;
        }

        //成功通过比对 一定是回文 此时将结构修改回去
        p2=pre;
        pre=next=null;
        while(p2!=null){
            next=p2.next;
            p2.next=pre;
            pre=p2;
            p2=next;
        }
        slower.next=p2;
        return true;
    }

    public static void main(String[] args) {
        Node n1=new Node(1);
        Node n2=new Node(2);
        Node n3=new Node(3);
        Node n4=new Node(2);
        Node n5=new Node(1);
        n1.next=n2;
        n2.next=n3;
        n3.next=n4;
        //n2.next=n4;
        n4.next=n5;

        System.out.println(ispalindrome2(n1));
    }

将单向链表按某值划分成左边小、中间相等、右边大的形式

【题目】 给定一个单向链表的头节点head,节点的值类型是整型,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于 pivot 的节点,中间部分都是值等于pivot的节点,右部分都是值大于 pivot的节点。 除这个要求外,对调整后的节点顺序没有更多的要求。 例如:链表9->0->4->5>1,pivot=3。 调整后链表可以是1->0->4->9->5,也可以是0->1->9->5->4。总 之,满 足左部分都是小于3的节点,中间部分都是等于3的节点(本例中这个部 分为空),右部分都是大于3的节点即可。对某部分内部的节点顺序不做要求。

其实就是考察如何对链表做荷兰国旗问题(paration过程),在没有额外空间复杂度要求下,可以将值拷贝进数组,在数组上做paration,将过程后的值用来重构链表里的值即可。

进阶: 在原问题的要求之上再增加如下两个要求。 在左、中、右三个部分的内部也做顺序要求,要求每部分里的节点从左 到右的 顺序与原链表中节点的先后次序一致。 例如:链表9->0->4->5->1,pivot=3。 调整后的链表是0->1->9->4->5。 在满足原问题要求的同时,左部分节点从左到 右为0、1。在原链表中也 是先出现0,后出现1;中间部分在本例中为空,不再 讨论;右部分节点 从左到右为9、4、5。在原链表中也是先出现9,然后出现4, 最后出现5。 如果链表长度为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)。

思路:准备三个桶,分别用来存储< = >pivot的节点,每次按照遍历的顺序依次往桶里加入节点,最后把三桶之间首尾相连即可,但是题目要求O(1)的额外空间复杂度,我们就不能真的使用桶,需要模拟出桶的存在。我们可以直接设置三个指针less、equal、more用来指向三个桶,同时为每个桶设置一个指向最后一个元素的tail指针。

方法1:先遍历一遍,给6个指针赋初始值;然后第二次遍历,将符合的值接在后面;最后将三个桶内的值串起来

public class ListParation {

    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }

    public static Node paration(Node head,int pivot){
        Node less=null,equal=null,more=null,p=head,lessTail=null,equalTail=null,moreTail=null;
        while(p!=null){
            if(p.value<pivot&&less==null)less=lessTail=p;
            else if(p.value==pivot&&equal==null)equal=equalTail=p;
            else if(p.value>pivot&&more==null)more=moreTail=p;
            p=p.next;
        }
        p=head;
        while(p!=null){
            if(p.value<pivot&&p!=lessTail){
                lessTail.next=p;
                lessTail=p;
            }
            else if(p.value==pivot&&p!=equalTail){
                equalTail.next=p;
                equalTail=p;
            }
            else if(p.value>pivot&&p!=moreTail){
                moreTail.next=p;
                moreTail=p;
            }
            p=p.next;
        }
        if(lessTail!=null)lessTail.next=null;
        if(equalTail!=null)equalTail.next=null;
        if(moreTail!=null)moreTail.next=null;
        head=less;
        p=lessTail;

        if(head==null){//考虑中桶时 发现左桶空
            head=equal;
            p=equalTail;
        }else{
            p.next=equal==null?p.next:equal;
            p=equal==null?p:equalTail;
        }

        if(head==null){//考虑右桶时 发现左中桶都空
            head=more;
        }else{
            p.next=more;
        }
        return head;
    }

    public static void printLinkedList(Node head){
        System.out.println("Linked List:");
        while(head!=null){
            System.out.println(head.value+" ");
            head=head.next;
        }
    }

    public static void main(String[] args) {
        Node n1=new Node(9);
        Node n2=new Node(0);
        Node n3=new Node(4);
        Node n4=new Node(5);
        Node n5=new Node(1);
        n1.next=n2;
        n2.next=n3;
        n3.next=n4;
        n4.next=n5;
        n1=paration(n1,3);
        printLinkedList(n1);
    }
}

方法2:直接给每个桶设置一个头节点,只需要一次遍历

    public static void main(String[] args) {
        Node n1=new Node(9);
        Node n2=new Node(0);
        Node n3=new Node(4);
        Node n4=new Node(5);
        Node n5=new Node(1);
        n1.next=n2;
        n2.next=n3;
        n3.next=n4;
        n4.next=n5;
        n1=paration2(n1,3);
        printLinkedList(n1);
    }

    public static Node paration2(Node head,int pivot) {
        Node less = null, equal = null, more = null, p , lessTail = null, equalTail = null, moreTail = null;
        less=lessTail=new Node(-1);//局部引用变量的默认值为null
        equal=equalTail=new Node(-1);
        more=moreTail=new Node(-1);
        while(head!=null){
            if(head.value<pivot){
                lessTail.next=head;
                lessTail=head;
            }
            else if(head.value==pivot){
                equalTail.next=head;
                equalTail=head;
            }
            else if(head.value>pivot){
                moreTail.next=head;
                moreTail=head;
            }
            head=head.next;
        }
        lessTail.next=equalTail.next=moreTail.next=null;
        head = less.next;
        p = lessTail;

        if(head==null){
            head=equal.next;
            p=equalTail;
        }else{//head不为空 所以p不可能为空指针
            p.next=equal.next;//equal是否等于null都不影响
            p=equal.next==null?p:equal.next;
        }

        if(head==null){
            head=more.next;
        }
        else{
            p.next=more.next;
        }
        return head;
    }

复制含有随机指针节点的链表

【题目】 一种特殊的链表节点类描述如下:

public class Node { public int value; public Node next; public Node rand; public Node(int data) { this.value = data; } }

Node类中的value是节点值,next指针和正常单链表中next指针的意义 一 样,都指向下一个节点,rand指针是Node类中新增的指针,这个指 针可 能指向链表中的任意一个节点,也可能指向null。 给定一个由 Node节点类型组成的无环单链表的头节点head,请实现一个 函数完成这个链表中所有结构的复制,并返回复制的新链表的头节点。

其实解决的关键在于我们怎么知道表中节点的指向关系。我们可以使用哈希表,存放的元素为<旧节点、新节点>,假设原本链表中有一个A->B的指向,我们在哈希表中可以存放<A,A'>、<B,B'>,这样通过A找到A',通过B找到B',于是根据A->B得到一条A'->B'的指向关系。

    public static Node copyLinkedListWithHash(Node head){
        HashMap<Node,Node> hashMap=new HashMap<>();
        Node p1=head,p2;
        while(p1!=null){
            p2=new Node(p1.value);
            hashMap.put(p1,p2);
            p1=p1.next;
        }
        p1=head;
        while(p1!=null){
            hashMap.get(p1).rand=hashMap.get(p1.rand);
            hashMap.get(p1).next=hashMap.get(p1.next);
            p1=p1.next;
        }
        return hashMap.get(head);
    }

    public static void printLinkedList(Node head){
        System.out.print("Linked List:");
        while(head!=null){
            System.out.print("now:"+head.value+" random:"+(head.rand==null?"null":head.rand.value)+" ");
            head=head.next;
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Node n1=new Node(1);
        Node n2=new Node(2);
        Node n3=new Node(3);
        n1.next=n2;n2.next=n3;
        n1.rand=n3;n2.rand=n1;n3.rand=null;
        printLinkedList(n1);
        Node h1=copyLinkedListWithHash(n1);
        printLinkedList(h1);
    }

进阶: 不使用额外的数据结构,只用有限几个变量,且在时间复杂度为 O(N) 内完成原问题要实现的函数。

分析上述思想,其实使用hash表的目的就是可以通过一个原始节点找到其对应的节点,从而对应在新节点间按照旧节点之间的关系重建链表。然而如果我们在原链表的每一个节点后面建立一个他的拷贝,即类似于将1->2->3建立为1->1'->2->2'->3->3',这样,隔节点重连我们就可以复制next关系,而假设原本有一条Random关系:2->1,我们就知道2.next应该指向2.random.next即让2'->1'。使用链表上的关系省去hash表。

public class CopyLinkedList {

    public static class Node {
        public int value;
        public Node next;
        public Node rand;
        public Node(int data) {
            this.value = data;
        }
    }

    public static Node copyLinkedList(Node head){
        Node p,h=head,ans;
        while(h!=null){//在每一个原始节点后面生成一个和他值相同的节点
            p=new Node(h.value);
            p.next=h.next;
            h.next=p;
            h=p.next;
        }
        h=head;
        while(h!=null){
            h.next.rand=h.rand==null?null:h.rand.next;
            h=h.next.next;
        }
        h=head;
        ans=p=head.next;
        while(p.next!=null){
            h.next=h.next.next;
            p.next=p.next.next;
            h=h.next;
            p=p.next;
        }
        return ans;
    }

    public static void printLinkedList(Node head){
        System.out.print("Linked List:");
        while(head!=null){
            System.out.print("now:"+head.value+" random:"+(head.rand==null?"null":head.rand.value)+" ");
            head=head.next;
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Node n1=new Node(1);
        Node n2=new Node(2);
        Node n3=new Node(3);
        n1.next=n2;n2.next=n3;
        n1.rand=n3;n2.rand=n1;n3.rand=null;
        printLinkedList(n1);
        Node h1=copyLinkedList(n1);
        printLinkedList(h1);
    }

}

两个单链表相交的一系列问题

【题目】 在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点 head1和head2,这两个链表可能相交,也可能 不相交。请实现一个函数, 如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null 即可。 要求:如果链表1 的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外 空间复杂度请达到O(1)。

可以分为三种链表是否相交的问题:

1.两个无环单链表是否相交【前面已解】

2.一个有环单链表和一个无环单链表是否相交【分析可知不存在这种情况】

我们设含有环的单链表分为链式部分(必在环部分之前)和环部分,假设另一条链表在链式部分和该链表相交,那么另一条链表应该会进入环,不符无环的设定。如果二者在环内相交,另一条链表还是应该有环,不符合条件。

3.两个有环单链表相交【分情况讨论】

1)二者不相交

2.二者在链式区域先相交【如果我们抛开环不看,那么就变成两个链表相交问题】

3.二者在环上相交

首先我们需要解决的是,对于一个有环的单链表,如果拿到它入环的第一个节点。诚然可以用哈希表,但是这里我们介绍一种数学解法,这需要用到快慢指针:一开始快慢指针都在头节点,然后在单链表上跑,直到相遇,此时让快指针回到开头,一次走一步,和慢指针同步更新,二者会在入环节点处相遇。

证明,我们设链式部分为L,环部分为R,经过了T时间二者相遇,则有下式:(2T-L)%R=(T-L)%R,由于快指针一定比慢指针走过的路程远,所以我们可以将上式简化为2T-L=(T-L)+kR,k>=1且kR>=L,否则慢指针都不会入环,快慢指针不可能相遇;可解得T=kR。此时我们可以算出相遇的位置距离入环节点的距离为:R-(kR-L)%R。如果R>=L,则化简为R-(R-L)=L,即相遇处距离入环节点的距离和链式部分的长度一致。如果R<L,那么可设L=p*R+t,【由kR>=L可知k>=p且=只在t=0时取到】,上式变为 R-(k*R-L)%R=R-(k*R-p*R-t)%R=t。也就是说,对于R<L的情况,相遇处离入环节点的距离为t,此时快指针回到开头,两者都跑p*R+t步后,会在入环节点处相遇。

public class FindFirstIntersectNode {

    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }

    public static Node noLoop(Node n1,Node n2){
        Node end1=n1,end2=n2;
        int count=0;
        while(end1!=null){
            ++count;
            end1=end1.next;
        }
        while(end2!=null){
            --count;
            end2=end2.next;
        }
        if(end1!=end2)return null;//终点不同的两个单链表不可能相交
        end1=count>0?n1:n2;//让end1指向长链表 end2指向短链表
        end2=end1==n1?n2:n1;
        count=count>0?count:-count;
        while(count!=0){
            --count;
            end1=end1.next;
        }
        while(end1!=end2){
            end1=end1.next;
            end2=end2.next;
        }
        return end1;
    }

    public static Node bothLoop(Node n1,Node loop1,Node n2,Node loop2){
        Node cur1=n1,cur2=n2;
        if(loop1==loop2){//入环节点相同 第二种情况  当作两个无环单链表处理
            int n=0;
            cur1=n1;
            cur2=n2;
            while(cur1!=loop1){
                ++n;
                cur1=cur1.next;
            }
            while(cur2!=loop1){
                --n;
                cur2=cur2.next;
            }
            cur1=n>0?n1:n2;
            cur2=cur1==n1?n2:n1;
            n=n>0?n:-n;
            while(n!=0){
                --n;
                cur1=cur1.next;
            }
            while(cur1!=cur2){
                cur1=cur1.next;
                cur2=cur2.next;
            }
            return cur1;
        }else{
            //先判断是否共有环,如果不共有,则是都有环但不相交的情况,如果共有,随便返回环上一点均正确
            cur1=loop1.next;
            while(cur1!=loop1){//走了一圈都没碰到loop2,证明不共有环
                if(cur1==loop2)return loop1;
                cur1=cur1.next;
            }
            return null;
        }
    }

    public static Node getIntersectNode(Node n1,Node n2){
        if(n1==null||n2==null){//两个链表中一个有空就不会存在第一个交点
            return null;
        }
        Node loop1=getLoopNode(n1);
        Node loop2=getLoopNode(n2);
        if(loop1==null&&loop2==null){//两个单链表的情况
            return noLoop(n1,n2);
        }
        if(loop1!=null&&loop2!=null){
            return bothLoop(n1,loop1,n2,loop2);
        }
        return null;//一个单链表一个有环是不可能有交点的
    }

    public static Node getLoopNode(Node head){// 拿到有环单链表的第一个入环节点
        Node first=head,slower=head;
        while(first!=null&&first.next!=null){
            first=first.next.next;
            slower=slower.next;
            if(first==slower)break;
        }
        if(first==null||first.next==null)return null;//快指针能指向空或者空之前的一个节点 说明不存在环
        first=head;
        while(first!=slower){
            first=first.next;
            slower=slower.next;
        }
        return first;
    }

    public static void main(String[] args) {
        // 1->2->3->4->5->6->7->null
        Node head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        head1.next.next.next = new Node(4);
        head1.next.next.next.next = new Node(5);
        head1.next.next.next.next.next = new Node(6);
        head1.next.next.next.next.next.next = new Node(7);

        // 0->9->8->6->7->null
        Node head2 = new Node(0);
        head2.next = new Node(9);
        head2.next.next = new Node(8);
        head2.next.next.next = head1.next.next.next.next.next; // 8->6
        System.out.println(getIntersectNode(head1, head2).value);

        // 1->2->3->4->5->6->7->4...
        head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        head1.next.next.next = new Node(4);
        head1.next.next.next.next = new Node(5);
        head1.next.next.next.next.next = new Node(6);
        head1.next.next.next.next.next.next = new Node(7);
        head1.next.next.next.next.next.next = head1.next.next.next; // 7->4

        // 0->9->8->2...
        head2 = new Node(0);
        head2.next = new Node(9);
        head2.next.next = new Node(8);
        head2.next.next.next = head1.next; // 8->2
        System.out.println(getIntersectNode(head1, head2).value);


        // 0->9->8->6->7->4->5->6..
        head2 = new Node(0);
        head2.next = new Node(9);
        head2.next.next = new Node(8);
        head2.next.next.next = head1.next.next.next.next.next; // 8->6
        System.out.println(getIntersectNode(head1, head2).value);
    }
}

关于二分的扩展:实际上二分的适用场景不应该是单纯的有序序列,而是每次通过判断某个值,可以帮助我们排除一部分区间。

二分的应用场景:

1)在一个有序数组中,查找某个值是否存在

2)在一个有序数组中,查找>=某个数的最左位置(从左到右第一个>=某个数的索引)

3)局部最小值问题(二分不需要一定有序的例子)可能有多个,只需要返回一个即可。【输入保证数组长度>=3】

局部最小值分为三种:  A.Arr[0]<Arr[1]    B.Arr[N-1]<Arr[N-2]   C.Arr[i-1]>Arr[i]<Arr[i+1] 保证在数组中,任意相邻位置的数不等

思路:在满足A时返回Arr[0],否则如果满足B,则返回Arr[N-1],否则可以对i进行二分搜索。当A、B的条件均不满足的时候,即有Arr[0]>Arr[1],Arr[N-1]>Arr[N-2],我们将数值以图的形式表示出来。

此刻,我们并不知道中间的点是如何的,但是这样一个折线图,要想满足开头下降最后上扬,则中间至少存在一个拐点i,满足Arr[i-1]>Arr[i]<Arr[i+1]。此时我们可以直接二分i,如果搜索值i满足Arr[i-1]>Arr[i]<Arr[i+1],则返回答案;否则进入判断:

如果Arr[i-1]<Arr[i],则相当于把原问题等效为0~i的问题【用i-1和i替代N-2和N-1】;如果Arr[i]>Arr[i+1],则等效于把原问题变为i~N-1的问题【用i和i+1替代0和1】。如果你要问Arr[i]大于两边的值呢,那可以证明左边和右边都有拐点,随便在哪个区间找咯。

public class LocalMinium {

    public static int binarySearchLocalMinium(int[] Arr){
        if(Arr[0]<Arr[1])return 0;
        if((Arr[Arr.length-1]<Arr[Arr.length-2]))return Arr.length-1;
        int L=0,R=Arr.length-1,Mid,ans;
        while(L<=R){
            Mid=(L+R)>>1;
            if(Arr[Mid]<Arr[Mid-1]&&Arr[Mid]<Arr[Mid+1]){
                return Mid;
            }
            if(Arr[Mid]>Arr[Mid-1]){
                R=Mid;
            }
            else {
                L=Mid;
            }
        }
        return -1;
    }


    public static void main(String[] args) {
        int[] Arr=new int[]{9,7,14,16,19,10,20};
        System.out.println(binarySearchLocalMinium(Arr));
    }
}

关于二分的模板总结:其实二分看起来好些写,但是其实还是有一些坑的,尤其是对于一些找大于等于target的最小值或者<=target的最大值的时候,下面介绍一些模板(以找到满足要求的最小值为例):

    //二分问题模板-写回法 每次的待定区间为[L,R]
    public static int binarySearch1(int L,int R){
        int ans=-1,Mid;
        while(L<=R){
            Mid=(L+R)>>1;
            if(check(Mid)){
                ans=Mid;
                R=Mid-1;
            }else{
                L=Mid+1;
            }
        }
        return ans;
    }
    //我们让[L,R]为可行域 每次当Mid符合要求的时候 不能将Mid略去 最后跳出循环的时候,L==R==answer
    public static int binarySearch2(int L,int R){
        int ans=-1,Mid;
        while(L<R){
            Mid=(L+R)>>1;
            if(check(Mid)){
                R=Mid;
            }else{
                L=Mid+1;
            }
        }
        return L;
    }

以一个例子来说一下:

现在有n个人,每个人哟与一个分数,现在有分数的升序数组,我们需要找到>=target的最小值。

显然,我们可以尽量让[L,R]是满足要求的,如果Mid位置的数>=target,为了找到尽可能小的,应该R=Mid,否则应该L=Mid+1。

上述代码均可。

But,如果题设改为找到<target的最大值。

假设Mid处的数>=target,显然应该R=Mid-1,否则L=Mid。但是,我们考虑L=2,R=3,此时mid=5/2=3,假设Mid处的数是<target的,我们会让L=Mid=2,显然会死循环,为什么呢?因为(L+R)>>1会导致Mid落在区间中间或者中点靠左,在上述条件下,找到的>=target的最小值,符合找到尽可能左边的数,而当前是找到<target的最大值,找的是尽可能右的值,所以我们需要将二分改为Mid=(L+R+1)>>1。以后可以根据是要找尽可能左还是尽可能右来确定二分公式。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值