牛客网算法小结(1)

一:排序

归并排序的应用

1. 小和问题

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。

    public static int minSum(int[] arr,int low,int high){
        if (low >= high)
            return 0;
        int sum = 0;
        int mid = low + ((high - low) / 2);
        //分别求出左边的子数组和右边的子数组中各自的最小和
        sum += minSum(arr,low,mid);
        sum += minSum(arr,mid+1,high);
        //再对合并后的数组求最小和
        sum += min(arr,low,mid,high);
        return sum;
    }
    public static int min(int[] arr,int low,int mid,int high) {
        int sum = 0;
        int [] temp = new int[high - low + 1];
        int k = 0;
        int i,j;
        //有较小的值就加上
        for (i = low,j = mid+1; i <= mid && j <= high;) {
            if (arr[i] < arr[j]){
                temp[k++] = arr[i];
                sum += arr[i++] * (high - j + 1);
            }
            else
                temp[k++] = arr[j++];

        }
        while (i <= mid)
            temp[k++] = arr[i++];
        while (j <= high)
            temp[k++] = arr[j++];
        i = low;
        for (k = 0; k <temp.length; k++) {
            arr[i++] = temp[k];
        }

        return sum;
    }

2. 逆序对问题

在一个数组中,左边的数如果比右边的数大,则折两个数构成一个逆序对,请打印所有逆序对。

    public static void Reverse(int[] arr,int low,int high){
        if (low >= high)
            return;
        int mid = low + ((high - low) / 2);
        //分别求出左边的子数组和右边的子数组中各自的逆序对
        Reverse(arr,low,mid);
        Reverse(arr,mid+1,high);
        //再对合并后的数组求逆序对
        reverse(arr,low,mid,high);
    }
    public static void reverse(int[] arr,int low,int mid,int high) {
        int [] temp = new int[high - low + 1];
        int k = 0;
        int i,j;
        //打印逆序对
        for (i = low,j = mid+1; i <= mid && j <= high;) {
            if (arr[i] < arr[j])
                temp[k++] = arr[i++];
            else
            {
                int x = i;
                while (x <= mid)
                    System.out.print("["+arr[x++]+","+arr[j]+"]\t");
                System.out.println();
                temp[k++] = arr[j++];
            }

        }
        while (i <= mid)
            temp[k++] = arr[i++];
        while (j <= high)
            temp[k++] = arr[j++];
        i = low;
        for (k = 0; k <temp.length; k++) {
            arr[i++] = temp[k];
        }
    }

二:查找

1. 二分查找

【题目】 定义局部最小的概念。arr长度为1时,arr[0]是局部最小。arr的长度为 N(N>1)时,如果arr[0]<arr[1],那么arr[0]是局部最小;如果arr[N1]<arr[N-2],那么arr[N-1]是局部最小;如果0<i<N-1,既有 arr[i]<arr[i-1],又有arr[i]<arr[i+1],那么arr[i]是局部最小。 给定无序数组arr,已知arr中任意两个相邻的数都不相等。写一个函数, 只需返回arr中任意一个局部最小出现的位置即可。

    public static int getPartMin(int[] arr){
        if (arr == null || arr.length == 0)
            return -1;
        if (arr.length == 1)
            return 0;
        return getPartMin(arr,0,arr.length - 1);
    }
    public static int getPartMin(int[] arr,int low,int high){
        if (low > high)
            return -1;
        if (arr[low] < arr[low+1])
            return low;
        if (arr[high] < arr[high-1])
            return high;
        return getPartMin(arr,low+1,high-1);
    }

三:线性表

1. 用数组结构实现大小固定的队列和栈

2. 实现一个特殊的栈

在实现栈的基本功能的基础上,再实现返 回栈中最小元素的操作。
【要求】

  1. pop、push、getMin操作的时间复杂度都是O(1)。
  2. 设计的栈类型可以使用现成的栈结构
public class MinStack {

    private Stack<Integer> stack;

    private Stack<Integer> minStack;

    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }

    public void push(int num){
        stack.push(num);
        if (minStack.isEmpty() || minStack.peek() >= num)
            minStack.push(num);
    }

    public int peek(){
        return stack.peek();
    }

    public int pop(){
        int num = stack.pop();
        if (!minStack.isEmpty() && minStack.peek() == num)
            minStack.pop();
        return num;
    }

    public int min(){
        return minStack.peek();
    }
}

3. 队列和栈的转换

如何仅用队列结构实现栈结构?

/**
 * 用队列实现栈
 */
public class QueueToStack {
    private Queue<Integer> in;
    private Queue<Integer> out;

    public QueueToStack() {
        in = new LinkedList<>();
        out = new LinkedList<>();
    }

    public void push(int num){
        in.offer(num);
    }

    public int peek(){
        while (in.size() > 1)
            out.offer(in.poll());
        int num = in.poll();
        out.offer(num);
        swap();
        return num;
    }

    public int pop(){
        while (in.size() > 1)
            out.offer(in.poll());
        int num = in.poll();
        swap();
        return num;
    }

    private void swap(){
        Queue<Integer> temp = in;
        in = out;
        out = temp;
    }
}

如何仅用栈结构实现队列结构?

/**
 * 用栈实现队列
 */
public class StackToQueue {
    private Stack<Integer> in;
    private Stack<Integer> out;

    public StackToQueue() {
        in = new Stack<>();
        out = new Stack<>();
    }

    public void offer(int num){
        in.push(num);
    }

    public int peek(){
        if (out.isEmpty()){
            while (!in.isEmpty())
                out.push(in.pop());
        }
        return out.peek();
    }

    public int poll(){
        if (out.isEmpty()){
            while (!in.isEmpty())
                out.push(in.pop());
        }
        return out.pop();
    }
}

4. 实现一种狗猫队列的结构

要求如下:

  • 用户可以调用add方法将cat类或dog类的实例放入队列中;
  • 用户可以调用pollAll方法,将队列中所有的实例按照进队列的先后顺序依次弹出;
  • 用户可以调用pollDog方法,将队列中dog类的实例按照进队列的先后顺序依次弹出;
  • 用户可以调用pollCat方法,将队列中cat类的实例按照进队列的先后顺序依次弹出;
  • 用户可以调用isEmpty方法,检查队列中是否还有dog或cat的实例;
  • 用户可以调用isDogEmpty方法,检查队列中是否有dog类的实例;
  • 用户可以调用isCatEmpty方法,检查队列中是否有cat类的实例。
/**
 * 猫狗队列
 */
class Animal{
    private String type;

    Animal(String type) {
        this.type = type;
    }

    String getType() {
        return type;
    }
}
class Cat extends Animal{
    private String name;
    Cat(String name) {
        super("cat");
        this.name = name;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}
class Dog extends Animal{
    private String name;
    Dog(String name) {
        super("dog");
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}

/**
 * 自定义扩展类,添加时间戳字段,用于区分入队时间
 */
class AnimalCount{
    private Animal animal;
    private long count;

    AnimalCount(Animal animal, long count) {
        this.animal = animal;
        this.count = count;
    }

    Animal getAnimal() {
        return animal;
    }

    long getCount() {
        return count;
    }

    public String getType(){
        return animal.getType();
    }
}
public class CatAndDogQueue {
    private Queue<AnimalCount> dogs = new LinkedList<>();

    private Queue<AnimalCount> cats = new LinkedList<>();

    //记录时间戳
    private int count;

    void add(Animal animal){
        AnimalCount animalCount = new AnimalCount(animal,count++);
        if (animal.getType().equals("cat"))
            cats.add(animalCount);
        else
            dogs.add(animalCount);
    }

    Animal pollAll(){
        Animal temp;
        if(isEmpty())
            return null;
        else if (isDogEmpty())
            temp =  cats.poll().getAnimal();
        else if(isCatEmpty())
            temp =  dogs.poll().getAnimal();
        else
            temp =  dogs.peek().getCount() > cats.peek().getCount() ? cats.poll().getAnimal() : dogs.poll().getAnimal();
        return temp;
    }

    Cat pollCat(){
        if (isCatEmpty())
            return null;
        return (Cat) cats.poll().getAnimal();
    }

    Dog pollDog(){
        if (isDogEmpty())
            return null;
        return (Dog) dogs.poll().getAnimal();
    }

    private boolean isEmpty(){
        return cats.isEmpty() || dogs.isEmpty();
    }

    private boolean isDogEmpty(){
        return dogs.isEmpty();
    }

    private boolean isCatEmpty(){
        return cats.isEmpty();
    }

    public static void main(String[] args) {
        Cat cat1 = new Cat("c1");
        Dog dog1 = new Dog("d1");
        Cat cat2 = new Cat("c2");
        Dog dog2 = new Dog("d2");
        CatAndDogQueue queue = new CatAndDogQueue();
        queue.add(cat1);
        queue.add(dog1);
        queue.add(cat2);
        queue.add(dog2);
        System.out.println(queue.pollAll());
        System.out.println(queue.pollAll());
        System.out.println(queue.pollCat());
        System.out.println(queue.pollDog());
    }
}

5. 设计RandomPool结构

【题目】 设计一种结构,在该结构中有如下三个功能:

  • insert(key):将某个key加入到该结构,做到不重复加入。
  • delete(key):将原本在结构中的某个key移除。
  • getRandom():等概率随机返回结构中的任何一个key。

【要求】 Insert、delete和getRandom方法的时间复杂度都是O(1)。

/**
 * 随机数据池
 */
public class RandomPool<Key> {
    private HashMap<Key,Integer> keys;
    private HashMap<Integer,Key> indexs;
    private int size;

    public RandomPool(){
        keys = new HashMap<>();
        indexs = new HashMap<>();
    }

    public void insert(Key key){
        keys.put(key,size);
        indexs.put(size,key);
        size++;
    }

    //把要删除的键与最后一个键交换,再删除
    public void delete(Key key){
        if (!keys.containsKey(key))
            return;
        int index = keys.get(key);
        Key temp = indexs.get(size-1);
        keys.put(temp,index);
        keys.remove(key);
        indexs.put(index,temp);
        indexs.remove(size-1);
        size--;
    }

    public Key getRandom(){
        int ran = (int)(Math.random()*(size));
        return indexs.get(ran);
    }
} 

6. 转圈打印矩阵

【题目】 给定一个整型矩阵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)。

扫描二维码关注公众号,回复: 6681561 查看本文章
/**
 * 环形打印矩形
 */
public class circleMatrix {
    public static void print(int[][] matrix,int x1,int y1,int x2,int y2){
        //base case
        if (x1 > x2 || y1 > y2)
            return;
        //只有一行
        else if (y1 == y2){
            for (int i = x1; i < x2; i++) {
                System.out.printf(matrix[i][y1]+",");
            }
        }
        //只有一列
        else if (x1 == x2){
            for (int i = y1; i < y2; i++) {
                System.out.printf(matrix[x1][i]+",");
            }
        }
        //先从左向右打印,再从上到下打印,之后从右往左打印,从下往上打印
        else {
            int i = x1,j = y1;
            while (j < y2){
                System.out.printf(matrix[i][j++]+",");
            }
            while (i < x2){
                System.out.printf(matrix[i++][j]+",");
            }
            while (j > y1){
                System.out.printf(matrix[i][j--]+",");
            }
            while (i > x1){
                System.out.printf(matrix[i--][j]+",");
            }
            //再将左上角和右下角的边界减小
            print(matrix,x1+1,y1+1,x2-1,y2-1);
        }
    }

}

7. “之”字形打印矩阵

【题目】 给定一个矩阵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 class zhiMatrix {
    public static void print(int[][] matrix,boolean flag,int x1,int y1){
        if (matrix == null || matrix[0] == null)
            return;
        if (y1 < 0 && x1 < 0)
            return;
        //临界条件,目标点超过右下角
        if (x1 > matrix.length - 1 && y1 > matrix[0].length - 1)
            return;
        if (flag){
            //目标超过右界,从左边继续向下
            if (y1 == matrix[0].length)
            {
                y1--;
                x1++;
            }
            //从右上角向左下角打印
            while (y1 >= 0 && x1 <= matrix.length - 1 && y1 <= matrix[0].length - 1)
                System.out.printf(matrix[x1++][y1--]+",");
            print(matrix,false,x1,y1+1);
        }
        else {
            //目标超过下界,从右边继续继续向上
            if (x1 == matrix.length)
            {
                x1--;
                y1++;
            }
            //从左下角向右上角打印
            while (x1 >= 0 && y1 <= matrix[0].length - 1 && x1 <= matrix.length - 1)
                System.out.printf(matrix[x1--][y1++]+",");
            print(matrix,true,x1+1,y1);
        }
    }
}

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

【题目】 给定一个有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)。

/**
 * 在行列都排好序的矩阵中找数
 */
public class FindNumber {
    public static boolean isExits(int[][] matrix,int num,int x,int y){
        //如果到达最左边或者到达最下边,失败
        if (x >= matrix.length || y < 0)
            return false;
        //右边的数比较大,往左边的找
        if (matrix[x][y] > num)
            return isExits(matrix,num,x,y-1);
        //上边的数比较小,往下边的找
        else if (matrix[x][y] < num)
            return isExits(matrix,num,x+1,y);
        //找到了
        else
            return true;
    }
    public static void main(String[] args) {
        int[][] matrix = {{0  ,1,   2 ,  5}, {2,   3,   4  , 7}, {4,   4,   4 , 8} ,{5 ,  7,   7,   9}};
        System.out.println("7:"+isExits(matrix,7,0,matrix[0].length-1));
        System.out.println("6:"+isExits(matrix,6,0,matrix[0].length-1));
    }
}

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

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

**
 * 打印两个有序链表的公共部分
 */
public class CommonLink {
    static class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }

        @Override
        public String toString() {
            return "ListNode{" +
                    "val=" + val +
                    ", next=" + next +
                    '}';
        }
    }
    //使用外排法,逐个查找
    public static void print(ListNode head1,ListNode head2){
        boolean found = false;
        while (head1 != null && head2 != null){
            if (head1.val < head2.val)
                head1 = head1.next;
            else if (head1.val > head2.val)
                head2 = head2.next;
            else
            {
                System.out.printf(head1.val+" ");
                head1 = head1.next;
                head2 = head2.next;
                found = true;
                continue;
            }
            //如果之前有找到,则现在的是新的公共部分,换行
            if (found)
            {
                System.out.println();
                found = false;
            }
        }
    }

    

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

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

**
 * 回文链表
 */
public class PalindromeLinked {
    static class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }

        @Override
        public String toString() {
            return "ListNode{" +
                    "val=" + val +
                    ", next=" + next +
                    '}';
        }
    }
    public static boolean isPalindrome(ListNode head){
        if (head == null)
            return false;
        if (head.next == null)
            return true;
        ListNode fast = head;
        ListNode slow = head;
        //快指针走两步,慢指针走一步
        while (fast != null){
            fast = fast.next;
            if (fast != null)
                fast = fast.next;
            slow = slow.next;
        }
        //当快指针到尾时,慢指针到达中点位置
        //使用两个记录分别前半段和后半段,再将后半段反转并与前半段逐个比较
        ArrayList<ListNode> list1 = new ArrayList<>();
        while (head != slow){
            list1.add(head);
            head = head.next;
        }
        ArrayList<ListNode> list2 = new ArrayList<>();
        while (slow != null){
            list2.add(slow);
            slow = slow.next;
        }
        Collections.reverse(list2);
        for (int i = 0; i < list1.size()-1; i++) {
            if (list1.get(i).val != list2.get(i).val)
                return false;
        }
        return true;
    }

    public static void main(String[] args) {
        ListNode head = new ListNode(1);
        ListNode node1 = new ListNode(2);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(1);
//        head.next = node1;node1.next = node2;node2.next = node3;
//        System.out.println(isPalindrome(head));
//        head.next = node1;node1.next = node2;
//        System.out.println(isPalindrome(head));
        head.next = node1;node1.next = node3;
        System.out.println(isPalindrome(head));
    }
}

    //栈法
    public static boolean isPalindrome2(ListNode head){
        if (head == null)
            return false;
        if (head.next == null)
            return true;
        ListNode fast = head;
        ListNode slow = head;
        //快指针走两步,慢指针走一步
        while (fast != null){
            fast = fast.next;
            if (fast != null)
                fast = fast.next;
            else
                continue;
            slow = slow.next;
        }
        //当快指针到尾时,慢指针到达中点位置
        //用快指针指向中点
        fast = slow;
        Stack<ListNode> stack = new Stack<>();
        while (slow != null){
            stack.push(slow);
            slow = slow.next;
        }
        while (head != fast){
            if (head.val != stack.pop().val)
                return false;
            head = head.next;
        }
        return true;
    }

    //空间复杂度O(1)
    public static boolean isPalindrome3(ListNode head) {
        if (head == null)
            return false;
        if (head.next == null)
            return true;
        ListNode fast = head;
        ListNode slow = head;
        //快指针走两步,慢指针走一步
        while (fast != null){
            fast = fast.next;
            if (fast != null)
                fast = fast.next;
            else
                continue;
            slow = slow.next;
        }
        //当快指针到尾时,慢指针到达中点位置
        //开始反转右部分的链表
        fast = slow.next;
        slow.next = null;
        ListNode node = null;
        while (fast != null){
            node = fast.next;
            fast.next = slow;
            slow = fast;
            fast = node;
        }
        //记录最右边的结点,方便恢复链表
        fast = slow;
        //逐个比较
        boolean res = true;
        while (head != null && slow != null){
            if (head.val != slow.val)
            {
                res =  false;
                break;
            }
            head = head.next;
            slow = slow.next;
        }
        //恢复右半部分
        while (fast != null){
            node = fast.next;
            fast.next = slow;
            slow = fast;
            fast = node;
        }
        return res;
    }
 

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

【题目】 给定一个单向链表的头节点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的节点即可。对某部分内部的节点顺序不做 要求。

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

/**
 * 划分链表
 */
public class DivideLinke {
    static class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }

        @Override
        public String toString() {
            return "ListNode{" +
                    "val=" + val +
                    ", next=" + next +
                    '}';
        }
    }
    public static void divide(ListNode head,int pivot){
        if (head == null)
            return;

        ListNode node = head;
        ListNode temp = null;

        Deque<ListNode> low = new ArrayDeque<>();
        Deque<ListNode> mid = new ArrayDeque<>();
        Deque<ListNode> high = new ArrayDeque<>();

        //开始划分,根据值放入不同的队列
        while (node != null){
            temp = node.next;
            node.next = null;
            if (node.val < pivot)
            {
                if (!low.isEmpty())
                    low.peek().next = node;
                low.push(node);
            }
            else if (node.val == pivot)
            {
                if (!mid.isEmpty())
                    mid.peek().next = node;
                mid.push(node);
            }
            else
            {
                if (!high.isEmpty())
                    high.peek().next = node;
                high.push(node);
            }
            node = temp;
        }

        //合并队列
        if (!low.isEmpty())
        {
            node = low.peek();
            head = low.peekLast();
        }
        if (!mid.isEmpty())
        {
            temp = mid.peekLast();
            if (node != null)
                node.next = temp;
            node = temp;
            if (low.isEmpty())
                head =  temp;
        }
        if (!high.isEmpty())
        {
            temp = high.peekLast();
            if (node != null)
            {
                node.next = temp;
            }
            if (low.isEmpty() && mid.isEmpty())
                head = temp;
        }
        System.out.println(head);
    }

    public static void main(String[] args) {
        ListNode head = new ListNode(9);
        ListNode node1 = new ListNode(0);
        ListNode node2 = new ListNode(4);
        ListNode node3 = new ListNode(5);
        ListNode node4 = new ListNode(1);
        head.next = node1;
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        System.out.println(head);
        divide(head,3);
    }
}

如果链表长度为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)。

    //空间复杂度O(1)
    public static void divide1(ListNode head,int pivot) {
        if (head == null)
            return;
        ListNode node = head;

        //左、中、右三部分分别设置头、尾和中间结点
        ListNode lowH = null;
        ListNode lowTemp = null;
        ListNode lowT = null;
        ListNode midH = null;
        ListNode midTemp = null;
        ListNode midT = null;
        ListNode highH = null;
        ListNode highTemp = null;
        ListNode highT = null;

        //开始循环
        while (node != null){
            node = head.next;
            head.next = null;
            if (pivot > head.val)
            {
                if (lowH == null)
                {
                    lowH = head;
                    lowTemp = head;
                }
                lowT = head;
                if (lowTemp != lowT)
                {
                    lowTemp.next = lowT;
                    lowTemp = lowT;
                }
            }
            else if (pivot == head.val){
                if (midH == null)
                {
                    midH = head;
                    midTemp = head;
                }
                midT = head;
                if (midTemp != midT)
                {
                    midTemp.next = midT;
                    midTemp = midT;
                }
            }
            else{
                if (highH == null)
                {
                    highH = head;
                    highTemp = head;
                }
                highT = head;
                if (highTemp != highT)
                {
                    highTemp.next = highT;
                    highTemp = highT;
                }
            }
            head = node;
        }

        //合并链表
        if (lowT != null)
        {
            head = lowH;
            if (midH != null)
                lowT.next = midH;
            else
                lowT.next = highH;
        }
        if (midT != null)
        {
            if (head == null)
                head = midH;
            midT.next = highH;
        }
        if (head == null)
            head = highH;
        System.out.println(head);

    }

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

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

   public static ListNode copy(ListNode head){
        if (head == null)
            return null;
    
        //使用一个哈希映射表映射旧的结点和新的结点,再单独处理next指针和rand指针
        HashMap<ListNode,ListNode> nodes = new HashMap<>();

        ListNode node = null;

        ListNode temp = head;

        ListNode newHead = null;

		//构建键值对
        while (temp != null){
            node = new ListNode(temp.val);
            if (newHead == null)
                newHead = node;
            nodes.put(temp,node);
            temp = temp.next;
        }

		//单独处理结点的指针
        while (head != null){
            nodes.get(head).next = nodes.get(head.next);
            nodes.get(head).rand = nodes.get(head.rand);
            head = head.next;
        }

        return newHead;
    }

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

    public static ListNode copy1(ListNode head){
        if (head == null)
            return null;

        ListNode node = null;

        ListNode temp = head;

        //在每个结点后面创建一个拷贝结点
        while (temp != null){
            node = new ListNode(temp.val);
            node.next = temp.next;
            temp.next = node;
            temp = node.next;
        }
        //拷贝rand指针
        temp = head;
        while (temp != null){
            if (temp.rand != null)
                temp.next.rand = temp.rand.next;
            temp = temp.next.next;
        }
        //分离链表
        temp = head;
        ListNode newHead = head.next;
        ListNode next = newHead;
        while (temp != null){
            node = next;
            temp.next = node.next;
            temp = temp.next;
            if (temp != null) {
                next = temp.next;
                node.next = next;
            }
        }
        return newHead;
    }

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

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

/**
 * 相交链表
 */
public class CrossLink {
    static class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }

        @Override
        public String toString() {
            return "ListNode{" +
                    "val=" + val +
                    ", next=" + next.val +
                    '}';
        }
    }

	//是否成环
    private static ListNode isCircle1(ListNode head){
        HashSet<ListNode> set = new HashSet<>();
        while (head != null){
            if (set.contains(head)){
                return head;
            }
            set.add(head);
            head = head.next;
        }
        return null;
    }


    //是否成环,空间复杂度O(1)
    private static ListNode isCircle(ListNode head){
        ListNode fast = head;
        ListNode slow = head;

        //快指针如果与慢指针相遇,则有环,改为走一步
        boolean meet = false;

        while (fast != null){
            fast = fast.next;
            if (fast!=null && !meet)
                fast = fast.next;
            slow = slow.next;
            if (fast == slow)
                if(!meet)
                    meet = true;
                else
                    return fast;
        }
        return null;
    }

	//是否相交
    public static ListNode isCross(ListNode head1,ListNode head2) {
        if (head1 == null || head2 == null)
            return null;
        ListNode circle1 = isCircle1(head1);
        ListNode circle2 = isCircle1(head2);

        //一个有环,一个没环,不会相交
        if ((circle1 == null && circle2 != null) || (circle1 != null && circle2 == null))
            return null;

        //两个都没环
        else if (circle1 == null && circle2 == null)
            return noCircle(head1,head2,null);
       //两个都是环
        else {
            int n = 0;
            ListNode temp1 = head1;
            ListNode temp2 = head2;
		
			//共享环,相交在非环区域
            if (circle1 == circle2)
                return noCircle(head1,head2,circle1);
           
            else {
                ListNode temp = circle1;
                circle1 = circle1.next;
                while (circle1 != temp) {
                	//共享环,相交在环区域
                    if (circle1 == circle2)
                        return circle1;
                    circle1 = circle1.next;
                }
                //非相交
                return null;
            }
        }
    }

	//两个非环的链表
    private static ListNode noCircle(ListNode head1,ListNode head2,ListNode cross){
        int n = 0;
        ListNode temp1 = head1;
        ListNode temp2 = head2;

        //先统计两个链表的长度
        while (temp1.next != cross){
            n++;
            temp1 = temp1.next;
        }
        while (temp2.next != cross){
            n--;
            temp2 = temp2.next;
        }
        if (temp1 != temp2)
            return null;

        //较长的链表先走N步
        temp1 = n >= 0 ? head1 : head2;
        temp2 = temp1 == head2 ? head1 : head2;

        n = Math.abs(n);
        while (0 < n--){
            temp1 = temp1.next;
        }

        //两个链表一起走,直到相交结点
        while (temp1 != temp2){
            temp1 = temp1.next;
            temp2 = temp2.next;
        }
        return temp1;
    }

    public static void main(String[] args) {
        ListNode head1 = new ListNode(1);
        ListNode node1 = new ListNode(2);
        ListNode node2 = new ListNode(3);
        ListNode node3 = new ListNode(4);
        ListNode node4 = new ListNode(5);
        ListNode head2 = new ListNode(6);
        ListNode node5 = new ListNode(7);
        ListNode node6 = new ListNode(8);
        ListNode node7 = new ListNode(9);
        ListNode node8 = new ListNode(10);

        //都无环
//        head.next = node1;
//        node1.next = node2;
//        node2.next = node3;
//        node3.next = node4;
//        node5.next = node3;
//        System.out.println(isCross(head,node5));

        //都有环且不相干
//        head1.next = node1;
//        node1.next = node2;
//        node2.next = node3;
//        node3.next = node4;
//        node4.next = node1;
//        head2.next = node5;
//        node5.next = node6;
//        node6.next = node7;
//        node7.next = node8;
//        node8.next = node5;
//        System.out.println(isCross(head1,head2));

        //都有环且相干
//        head1.next = node1;
//        node1.next = node2;
//        node2.next = node3;
//        node3.next = node4;
//        node4.next = node1;
//        head2.next = node2;
//        System.out.println(isCross(head1,head2));
        //都有环且相干
        head1.next = node1;
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node1;
        head2.next = head1;
        System.out.println(isCross(head1,head2));

    }
}

14. 反转单向和双向链表

【题目】 分别实现反转单向链表和反转双向链表的函数。
【要求】 如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)

/**
 * 反转链表
 */
public class ReverseLink {
    static class  ListNode1 {
        int val;
         ListNode1 next = null;

         ListNode1(int val) {
            this.val = val;
        }

        @Override
        public String toString() {
            return " ListNode1{" +
                    "val=" + val +
                    ", next=" + next +
                    '}';
        }
    }
    static class  ListNode2 {
        int val;
         ListNode2 next = null;
         ListNode2 prev = null;

         ListNode2(int val) {
            this.val = val;
        }

        @Override
        public String toString() {
            return " ListNode2{" +
                    "val=" + val +
                    ", next=" + next +
                    ", prev=" + (prev == null ? null : prev.val) +
                    '}';
        }
    }
    
    //单链表
    public static ListNode1 reverse1(ListNode1 head){
        if (head == null)
            return null;
        ListNode1 node = head.next;
        head.next = null;
        ListNode1 temp = null;
        while (node != null){
            temp = node.next;
            node.next = head;
            head = node;
            node = temp;
        }
        return head;
    }

    //双链表
    public static ListNode2 reverse2(ListNode2 head) {
        if (head == null)
            return null;
        ListNode2 next = head.next;
        ListNode2 prev = head.prev;
        head.next = null;
        head.prev = null;
        ListNode2 temp = null;
        while (next != null){
            temp = next.next;
            next.next = head;
            head.next = prev;
            head.prev = next;
            prev = head;
            head = next;
            next = temp;
        }
        head.prev = null;
        return head;
    }
    public static void main(String[] args) {
        //单链表
//        ListNode1 head = new  ListNode1(1);
//        ListNode1 node1 = new  ListNode1(2);
//        ListNode1 node2 = new  ListNode1(3);
//        ListNode1 node3 = new  ListNode1(4);
//        ListNode1 node4 = new  ListNode1(5);
//        head.next = node1;
//        node1.next = node2;
//        node2.next = node3;
//        node3.next = node4;
//        System.out.println(head);
//        System.out.println(reverse1(head));
        
        //双链表
        ListNode2 head = new  ListNode2(1);
        ListNode2 node1 = new  ListNode2(2);
        ListNode2 node2 = new  ListNode2(3);
        ListNode2 node3 = new  ListNode2(4);
        ListNode2 node4 = new  ListNode2(5);
        head.next = node1;
        node1.next = node2;
        node1.prev = head;
        node2.next = node3;
        node2.prev = node1;
        node3.next = node4;
        node3.prev = node2;
        node4.prev = node3;
        System.out.println(head);
        System.out.println(reverse2(head));
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_36904568/article/details/93384136
今日推荐