阅读 277

新手入门篇-二分查找、局部最小、链表问题

“这是我参与更文挑战的第14天,活动详情查看: 更文挑战

上一篇是数据结构与算法新手入门第二篇,本篇是数据结构与算法新手入门第三篇,快来体验一下算法的魅力吧!

二分法

有序数组中找到num

思路:数组是有序的,从小到大排序,利用这个特性,可以二分查找法,查找的数与中间的数比大小,如果中间的数大于查找的数,说明查找的数在左边,R = mid - 1。如果中间的数小于查找的数,说明查找的数在右边,L = mid + 1。

public class Code01_BSExist {

    // 有序数组
    public static boolean find(int[] arr, int num) {
        if (arr == null || arr.length == 0) {
            return false;
        }
        int L = 0;
        int R = arr.length - 1;
        while (L <= R) {
            int middle = (L + R) / 2;
            if (arr[middle] == num) {
                return true;
            } else if (arr[middle] < num) {
                L = middle + 1;
            } else {
                R = middle - 1;
            }
        }
        return false;
    }

    // for test
    public static boolean test(int[] arr, int num) {
        for (int cur : arr) {
            if (cur == num) {
                return true;
            }
        }
        return false;
    }

    // for test
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int len = (int) (Math.random() * maxSize) + 1;
        int[] arr = new int[len];
        for (int i = 0; i < len; i++) {
            int ans = (int) (Math.random() * maxValue);
            arr[i] = ans;
        }
        return arr;
    }

    public static void main(String[] args) {
        int testTimes = 1000000;
        int maxSize = 100;
        int maxValue = 1000;
        boolean succeed = true;
        for (int i = 0; i < testTimes; i++) {
            int[] arr = generateRandomArray(maxSize, maxValue);
            Arrays.sort(arr);
            int num = (int) (Math.random() * maxValue);
            if (find(arr, num) != test(arr, num)) {
                System.out.println("出错了!");
                succeed = false;
                break;
            }
        }
        System.out.println(succeed ? "Nice!" : "Fucking fucked!");
    }
}
复制代码

有序数组中找到>=num最左的位置

思路:利用二分查找法,如果中间数大于查找的数,说明查找的数在左边,则 R = mid - 1,此时需要用变量记住当前索引位置,如果中间数小于查找的数,说明查找的数在右边,则 L = mid + 1,此时不需要用变量记录当前索引,因为题目要求>=num最左的位置,而从中间的数左半部分都小于num,就不用考虑了。

public class Code02_BSNearLeft {

    // arr有序的,>=num 最左
    public static int mostLeftNoLessNumIndex(int[] arr, int num) {
        if (arr == null || arr.length == 0) {
            return -1;
        }
        int L = 0;
        int R = arr.length - 1;
        int ans = -1;
        while (L <= R) {
            int middle = (L + R) / 2;
            if (arr[middle] >= num) {
                R = middle - 1;
                ans = middle;
            } else {
                L = middle + 1;
            }
        }
        return ans;
    }
    
    // for test
    public static int test(int[] arr, int value) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] >= value) {
                return i;
            }
        }
        return -1;
    }

    // for test
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
        }
        return arr;
    }

    // for test
    public static void printArray(int[] arr) {
        if (arr == null) {
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int testTime = 500000;
        int maxSize = 10;
        int maxValue = 100;
        boolean succeed = true;
        for (int i = 0; i < testTime; i++) {
            int[] arr = generateRandomArray(maxSize, maxValue);
            Arrays.sort(arr);
            int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
            if (test(arr, value) != mostLeftNoLessNumIndex(arr, value)) {
                printArray(arr);
                System.out.println(value);
                System.out.println(test(arr, value));
                System.out.println(mostLeftNoLessNumIndex(arr, value));
                succeed = false;
                break;
            }
        }
        System.out.println(succeed ? "Nice!" : "Fucking fucked!");
    }

}

复制代码

有序数组中找到<=num最右的位置

思路:利用二分查找法,如果中间的数小于查找的数,则 L = mid + 1,记录当前索引的位置。如果中间的数大于查找的数,则 R = mid - 1,此时不需要记录当前索引的位置,因为题目要求<=num最有的位置,中间的数及右边部分都大于查找的数,直接排除。

public class Code03_BSNearRight {

    // 在arr上,找满足<=value的最右位置
    public static int nearestIndex(int[] arr, int value) {
        int L = 0;
        int R = arr.length - 1;
        int index = -1; // 记录最右的对号
        while (L <= R) {
            int mid = L + ((R - L) >> 1);
            if (arr[mid] <= value) {
                index = mid;
                L = mid + 1;
            } else {
                R = mid - 1;
            }
        }
        return index;
    }

    // for test
    public static int test(int[] arr, int value) {
        for (int i = arr.length - 1; i >= 0; i--) {
            if (arr[i] <= value) {
                return i;
            }
        }
        return -1;
    }

    // for test
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
        }
        return arr;
    }

    // for test
    public static void printArray(int[] arr) {
        if (arr == null) {
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int testTime = 500000;
        int maxSize = 10;
        int maxValue = 100;
        boolean succeed = true;
        for (int i = 0; i < testTime; i++) {
            int[] arr = generateRandomArray(maxSize, maxValue);
            Arrays.sort(arr);
            int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
            if (test(arr, value) != nearestIndex(arr, value)) {
                printArray(arr);
                System.out.println(value);
                System.out.println(test(arr, value));
                System.out.println(nearestIndex(arr, value));
                succeed = false;
                break;
            }
        }
        System.out.println(succeed ? "Nice!" : "Fucking fucked!");
    }
}

复制代码

局部最小值问题

局部最小值问题推出二分查找的前提,数组不一定必须是有序的!!!

思路:如果数组只有一个元素,则返回,如果数组有2个元素,则比较2个元素的大小,进行返回索引位。抛出这两种可能外,则L...R 肯定有最小值,局部最小值问题,它不是说在数组里找找最小值,而是局部只要最小就行,只要当前这个数比左边小,且比右边小,就满足局部最小原则。

public class Code04_BSAwesome {

    // arr 整体无序
    // arr 相邻的数不相等!
    public static int oneMinIndex(int[] arr) {
        if (arr == null || arr.length == 0) {
            return -1;
        }
        int N = arr.length;
        if (N == 1) {
            return 0;
        }
        if (arr[0] < arr[1]) {
            return 0;
        }
        if (arr[N - 1] < arr[N - 2]) {
            return N - 1;
        }
        int L = 0;
        int R = N - 1;
        // L...R 肯定有局部最小
        while (L < R - 1) {
            int mid = (L + R) / 2;
            if (arr[mid] < arr[mid - 1] && arr[mid] < arr[mid + 1]) {
                return mid;
            } else {
                if (arr[mid] > arr[mid - 1]) {
                    R = mid - 1;
                } else {
                    L = mid + 1;
                }
            }
        }
        return arr[L] < arr[R] ? L : R;
    }

    // 生成随机数组,且相邻数不相等
    public static int[] randomArray(int maxLen, int maxValue) {
        int len = (int) (Math.random() * maxLen);// [0,maxLen - 1]
        int[] arr = new int[len];
        if (len > 0) {
            arr[0] = (int) (Math.random() * maxValue);
            for (int i = 1; i < len; i++) {
                do {
                    arr[i] = (int) (Math.random() * maxValue);
                } while (arr[i] == arr[i - 1]);
            }
        }
        return arr;
    }

    // 也用于测试
    public static boolean check(int[] arr, int minIndex) {
        if (arr.length == 0) {
            return minIndex == -1;
        }
        int left = minIndex - 1;
        int right = minIndex + 1;
        boolean leftBigger = left >= 0 ? arr[left] > arr[minIndex] : true;
        boolean rightBigger = right < arr.length ? arr[right] > arr[minIndex] : true;
        return leftBigger && rightBigger;
    }

    public static void printArray(int[] arr) {
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int maxLen = 100;
        int maxValue = 200;
        int testTime = 1000000;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            int[] arr = randomArray(maxLen, maxValue);
            int ans = oneMinIndex(arr);
            if (!check(arr, ans)) {
                printArray(arr);
                System.out.println(ans);
                break;
            }
        }
        System.out.println("测试结束");
    }
}

复制代码

单链表和双链表的定义

单链表:值,一条next指针

双链表:值,一条pre指针,一条next指针

玩出无尽的花样!

单双链表的反转

经典题目:

给定一个单链表的头head,完成链表的逆序调整

public class Code01_ReverseList {

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

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

    // 单链表反转
    public static Node reverseLinkedList(Node head) {
        Node pre = null;
        Node next = null;
        while (head != null) {
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }

    public static void main(String[] args) {
        // 单链表反转测试
        Node node1 = new Node(1);
        node1.next = new Node(2);
        node1.next.next = new Node(3);

        Node head = reverseLinkedList(node1);
        while (head != null) {
            System.out.print(head.value + " ");
            head = head.next;
        }
        System.out.println();
    }
}
复制代码

给定一个双链表的头head,完成链表的逆序调整

public class Code01_ReverseList {

    public static class DoubleNode {
        public int value;
        public DoubleNode last;
        public DoubleNode next;

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

        @Override
        public String toString() {
            return value + "";
        }
    }

    // 双链表反转
    public static DoubleNode reverseDoubleList(DoubleNode head) {
        DoubleNode pre = null;
        DoubleNode next = null;
        while (head != null) {
            next = head.next;
            head.next = pre;
            head.last = next;
            pre = head;
            head = next;

        }
        return pre;
    }

    public static void main(String[] args) {
        // 双链表反转测试
        DoubleNode doubleNode1 = new DoubleNode(1);
        doubleNode1.next = new DoubleNode(2);
        doubleNode1.next.next = new DoubleNode(3);
        doubleNode1.next.next.last = doubleNode1.next;
        doubleNode1.next.last = doubleNode1;

        DoubleNode doubleNodeHead = reverseDoubleList(doubleNode1);
        while (doubleNodeHead != null) {
            System.out.println(doubleNodeHead.next + "<-" + doubleNodeHead + "->" + doubleNodeHead.last);
            doubleNodeHead = doubleNodeHead.next;
        }
        System.out.println();
    }
}
复制代码

用单链表实现队列和栈

用单链表结构实现队列结构

public class Code02_LinkedListToQueueAndStack {

    public static class Node<V> {
        public V value;
        public Node<V> next;

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

    public static class MyQueue<V> {
        private Node<V> head;
        private Node<V> tail;
        private int size;

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

        public int size() {
            return size;
        }

        public void offer(V data) {
            Node<V> cur = new Node<>(data);
            if (tail == null) {
                head = tail = cur;
            } else {
                tail.next = cur;
                tail = cur;
            }
            size++;
        }

        public V poll() {
            V ans = null;
            if (head != null) {
                ans = head.value;
                head = head.next;
                size--;
            }
            if (head == null) {
                tail = null;
            }
            return ans;
        }

        public V peek() {
            V ans = null;
            if (head != null) {
                ans = head.value;
            }
            return ans;
        }
    }

    public static void main(String[] args) {
        MyQueue queue = new MyQueue();
        queue.offer(1);
        queue.offer(3);
        queue.offer(2);
        while (!queue.isEmpty()) {
            System.out.print(queue.poll() + " ");
        }
        System.out.println();
    }
}

复制代码

用单链表结构实现栈结构

public class Code02_LinkedListToQueueAndStack {

    public static class Node<V> {
        public V value;
        public Node<V> next;

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

    public static class MyStack<V> {
        private Node<V> head;
        private int size;

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

        public int size() {
            return size;
        }

        public void push(V data) {
            Node<V> cur = new Node<>(data);
            if (head != null) {
                cur.next = head;
            }
            head = cur;
            size++;
        }

        public V pop() {
            V ans = null;
            if (head != null) {
                ans = head.value;
                head = head.next;
                size--;
            }
            return ans;
        }

        public V peek() {
            return head != null ? head.value : null;
        }

    }

    public static void main(String[] args) {
        MyStack stack = new MyStack();
        stack.push(6);
        stack.push(1);
        stack.push(3);
        while (!stack.isEmpty()) {
            System.out.print(stack.pop() + " ");
        }
        System.out.println();
    }

}

复制代码

用双链表实现双端队列

用双链表结构实现双端队列

public class Code03_DoubleLinkedListDeque {

    public static class Node<V> {
        public V value;
        public Node<V> next;
        public Node<V> last;

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

    public static class MyDeque<V> {
        private Node<V> head;
        private Node<V> tail;
        private int size;

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

        public int size() {
            return size;
        }

        // 头部添加
        public void pushHead(V data) {
            Node<V> cur = new Node<>(data);
            if (head == null) {
                head = tail = cur;
            } else {
                cur.next = head;
                head.last = cur;
                head = cur;
            }
            size++;
        }

        // 尾部添加
        public void pushTail(V data) {
            Node<V> cur = new Node<>(data);
            if (head == null) {
                head = tail = cur;
            } else {
                tail.next = cur;
                cur.last = tail;
                tail = cur;
            }
            size++;
        }

        // 头部取出
        public V pollHead() {
            V ans = null;
            if (head == null) {
                return ans;
            }
            size--;
            ans = head.value;
            if (head == tail) {
                head = tail = null;
            } else {
                head = head.next;
                head.last = null;
            }
            return ans;
        }

        // 尾部取出
        public V pollTail() {
            V ans = null;
            if (head == null) {
                return ans;
            }
            size--;
            ans = tail.value;
            if (head == tail) {
                head = tail = null;
            } else {
                tail = tail.last;
                tail.next = null;
            }
            return ans;
        }

        // 查看头部
        public V peekHead() {
            return head != null ? head.value : null;
        }

        // 查看尾部
        public V peekTail() {
            return tail != null ? tail.value : null;
        }
    }

}

复制代码

K个节点的组内逆序调整

给定一个单链表的头节点head,和一个正数k,实现k个节点的小组内部逆序,如果最后一组不够k个就不调整。

例子:

调整前:1->2->3->4->5->6->7->8 k = 3

调整后:3->2->1->6->5->4->7->8

public class Code04_ReverseNodesInKGroup {

    public static class ListNode {
        public int val;
        public ListNode next;

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

    public static ListNode reverseKGroup(ListNode head, int k) {
        ListNode start = head;
        ListNode end = getKGroupEnd(start, k);
        if (end == null) {
            return head;
        }
        head = end;
        reverse(start, end);
        ListNode lastEnd = start;
        while (lastEnd.next != null) {
            start = lastEnd.next;
            end = getKGroupEnd(start, k);
            if (end == null) {
                return head;
            }
            reverse(start, end);
            lastEnd.next = end;
            lastEnd = start;
        }
        return head;
    }

    // 从start开始,返回k位置上的节点
    public static ListNode getKGroupEnd(ListNode start, int k) {
        while (--k != 0 && start != null) {
            start = start.next;
        }
        return start;
    }

    // 从start到end,反转链表
    public static void reverse(ListNode start, ListNode end) {
        end = end.next;
        ListNode cur = start;
        ListNode pre = null;
        ListNode next = null;
        while (cur != end) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        start.next = end;
    }

    public static void print(ListNode head) {
        while (head != null) {
            System.out.print(head.val + "->");
            head = head.next;
        }
        System.out.println();
    }

    public static void main(String[] args) {
        // 1->2->3->4->5->6->7->8 k = 3
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        ListNode node4 = new ListNode(4);
        ListNode node5 = new ListNode(5);
        ListNode node6 = new ListNode(6);
        ListNode node7 = new ListNode(7);
        ListNode node8 = new ListNode(8);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        node5.next = node6;
        node6.next = node7;
        node7.next = node8;

        print(node1);
        // 开始
        ListNode head = reverseKGroup(node1, 3);
        print(head);

    }

}
复制代码

两个链表相加

给定两个链表的头节点head1和head2,认为从左到右是某个数字从低位到高位,返回相加之后的链表

例子 4->3->6 2->5->3

返回 6->8->9

解释 634 + 352 = 986

public class Code05_AddTwoNumbers {

    public static class ListNode {
        private int val;
        private ListNode next;

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

    public static ListNode addTwoNumbers(ListNode head1, ListNode head2) {
        int len1 = listLength(head1);
        int len2 = listLength(head2);
        ListNode l = len1 >= len2 ? head1 : head2;
        ListNode s = l == head1 ? head2 : head1;
        ListNode curL = l;
        ListNode curS = s;
        ListNode last = curL;
        int carry = 0;
        int curNum = 0;
        while (curS != null) {
            curNum = curL.val + curS.val + carry;
            curL.val = (curNum % 10);
            carry = curNum / 10;
            last = curL;
            curL = curL.next;
            curS = curS.next;
        }
        while (curL != null) {
            curNum = curL.val + carry;
            curL.val = (curNum % 10);
            carry = curNum / 10;
            last = curL;
            curL = curL.next;
        }
        if (carry != 0) {
            last.next = new ListNode(1);
        }
        return l;
    }

    // 求链表长度
    public static int listLength(ListNode head) {
        int len = 0;
        while (head != null) {
            len++;
            head = head.next;
        }
        return len;
    }
}
复制代码

两个有序链表的合并

给定两个有序链表的头节点head1和head2,返回合并之后的大链表,要求依然有序。

例子 1->3->3->5->7 2->2->3->3->7

返回 1->2->2->3->3->3->3->5->7->7

public class Code06_MergeTwoSortedLinkedList {

    public static class ListNode {
        public int val;
        public ListNode next;

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

    public static ListNode mergeTwoSortedLinkedList(ListNode head1, ListNode head2) {
        if (head1 == null || head2 == null) {
            return head1 == null ? head2 : head1;
        }
        // 从两个有序的链表中找见头位置,谁小谁做头
        ListNode head = head1.val <= head2.val ? head1 : head2;
        ListNode cur1 = head.next;
        ListNode cur2 = head == head1 ? head2 : head1;
        ListNode pre = head;
        while (cur1 != null && cur2 != null) {
            if (cur1.val <= cur2.val) {
                pre.next = cur1;
                cur1 = cur1.next;
            } else {
                pre.next = cur2;
                cur2 = cur2.next;

            }
            pre = pre.next;
        }
        pre.next = cur1 != null ? cur1 : cur2;
        return head;
    }

    public static void main(String[] args) {
        ListNode firstListNode = new ListNode(1);
        firstListNode.next = new ListNode(3);
        firstListNode.next.next = new ListNode(5);

        ListNode secondListNode = new ListNode(1);
        secondListNode.next = new ListNode(2);

        ListNode head = mergeTwoSortedLinkedList(firstListNode, secondListNode);
        while (head != null) {
            System.out.print(head.val + " ");
            head = head.next;
        }
        System.out.println();
    }
}

复制代码

欢迎大家关注公众号(MarkZoe)互相学习、互相交流。

文章分类
后端
文章标签