【算法】解题总结:剑指Offer 6 旋转数组的最小数字、剑指Offer 16 合并两个排序的链表

JZ6 旋转数组的最小数字

(简单)

题目

描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

示例
输入:
[3,4,5,1,2]
返回值:
1

思路

本题中关于此原始数组的其中一个描述是非递减排序的数组,这个名词我们千万不能理解错了,非递减排序的正确意思是,数据递增排列,但并非单调递增(因为可能有多个相等的数据的情况),即数列整体上从小到大并且允许其中有相等的情况出现。

而此题中的旋转数组,也就是根据非递减排序的数组经过旋转生成的,因此,针对 [1, 2, 3, 4, 5]这样一个非递减排序的数组,经过旋转后的全部可能排列情况为:

12345

23451

34512

45123

51234

因为旋转后的数组,在整体上,仍是有序的,并且这种有序的性质是可能集中有序于左部分,或右部分,因此,我们不难联想到二分法,我们完全可以定义一个 start 起始下标,end 末尾下标,mid 中间下标。我们在编码的循环中,以 start != end 为终止循环的条件,并在循环体中不断缩小查找范围,最终定位最小元素值。接下来我们便可以根据上面的排序样例分析,在循环体中,如果中值大于尾值,那么我们可以判定最小值在位置的右半部分,并且最小值不可能是中值或者说不可能和中值相等,因此我们便可将头值下标指向中值下标的下一位,若中值小于尾值,则最小值在左部分,并且需将尾下标指向中值下标,因为中值也在最小值的可能范围内。最后还需注意的一点就是题目中的 NOTE:若数组大小为0,则返回0。

上述算法思想也可以再进行进一步的优化,就12345这种排序情况,我们没必要再进入循环体比较,因为此题情况特殊,是由非递减排序的数组而生成的旋转数组,因此,当程序遇到这种整体递增(可能有等值,但不影响)的情况,只需通过 array[start] 小于 array[end] 成立,即可判断为此种情况,直接返回头值即可。另外,如果在其他情况中,循环体中出现了 array[mid] 等于 array[end] 的情况(这也意味着原始的非递减排序有数据相等的情况出现),我们可将尾下标进行一次左移动,从而避免之后不必要的判断,提高算法的执行效率,而此题也是卡的这一点,因为我在用 Java 代码进行提交时,没有这一步优化是会运行超时的。

实现

class Solution3 {
    public int minNumberInRotateArray(int [] array) {
        if (array.length == 0) {
            return 0;
        }
        int start = 0;
        int end = array.length - 1;
        int mid = 0;
        while (start != end) {

            // 优化1
            if (array[start] < array[end]) {
                return array[start];
            }
            //

            mid = (start + end) / 2;
            if (array[mid] > array[end]) {
                start = mid + 1;
            } else if (array[mid] < array[end]) {
                end = mid;
            }

            // 优化2
            else {
                end--;
            }
            //
        }
        return array[start];
    }
}

public class JZ6旋转数组的最小数字 {
    public static void main(String[] args) {
        Solution3 solution3 = new Solution3();
        int[] array = new int[]{3, 4, 5, 1, 2};
        int[] array2 = new int[]{4, 5, 1, 2, 3};
        int[] array3 = new int[]{5, 1, 2, 3, 4};
        int[] array4 = new int[]{3, 4, 1, 2};
        int[] array5 = new int[]{2, 1};
        int[] array6 = new int[]{1, 2, 3, 4};
        System.out.println(solution3.minNumberInRotateArray(array));
        System.out.println(solution3.minNumberInRotateArray(array2));
        System.out.println(solution3.minNumberInRotateArray(array3));
        System.out.println(solution3.minNumberInRotateArray(array4));
        System.out.println(solution3.minNumberInRotateArray(array5));
        System.out.println(solution3.minNumberInRotateArray(array6));
    }
}

【算法】解题总结:剑指Offer 6 旋转数组的最小数字、剑指Offer 16 合并两个排序的链表_第1张图片

JZ16 合并两个排序的链表

(简单)

题目

描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

示例
输入:
{1,3,5},{2,4,6}
返回值:
{1,2,3,4,5,6}

思路

此题为常见的两链表合并操作,只是在合并的同时要求结点值单调不递减。

在合并方法中,我们首先判断两个子链表的自身情况,若全为 null,那么我们返回 null 即可,若其中一个为 null,则我们返回另一个即可,而剩余的一种情况,即两个子链表都不为空时,我们需要首先建立一个存储最终合并结果的链表,而此链表我建议加上头结点这样就可以使得此合并链表在空和非空状态时,能够保证操作的一致性(如果这里有疑问,自己两种方法都试一次,即有头结点,无头结点,便可知哪种是最优的),每次根据两个子链表的当前结点值进行比较,用较小的值,创建一个新的结点,并作为新结点的值,加入到合并链表中,不断重复此操作,直到其中一个链表已遍历结束,最终判断另一链表是否已遍历结束,若没有,则将其剩余结点按顺序加入到合并链表即可,因为题目中子链表本身的条件就是单调递增的,最终返回链表的第一个有效值结点即可,而不是返回整个链表,因为题中给出的测试数据便是这样要求的。

实现

class ListNode {
    int val;
    ListNode next = null;

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

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

class Solution6 {
    public static ListNode Merge(ListNode list1,ListNode list2) {
        if (list1 == null && list2 == null) {
            return null;
        } else if (list1 == null) {
            return list2;
        } else if (list2 == null) {
            return list1;
        } else {
            ListNode mergeList = new ListNode(0);
            ListNode tempList = mergeList;
            while (list1 != null && list2 != null) {
                if (list1.val > list2.val) {
                    tempList.next = new ListNode(list2.val);
                    tempList = tempList.next;
                    list2 = list2.next;
                } else {
                    tempList.next = new ListNode(list1.val);
                    tempList = tempList.next;
                    list1 = list1.next;
                }
            }
            while (list1 != null) {
                tempList.next = new ListNode(list1.val);
                tempList = tempList.next;
                list1 = list1.next;
            }
            while (list2 != null) {
                tempList.next = new ListNode(list2.val);
                tempList = tempList.next;
                list2 = list2.next;
            }
            return mergeList.next;
        }
    }
}

public class JZ16合并两个排序的链表 {
    public static void main(String[] args) {
        ListNode list1 = new ListNode(1);
        //ListNode OList1 = list1;
        //list1 = list1.next;
        //list1 = new ListNode(3);
        //list1 = list1.next;
        //list1 = new ListNode(5);
        list1.next = new ListNode(3);
        list1.next.next = new ListNode(5);

        ListNode list2 = new ListNode(2);
        //ListNode OList2 = list2;
        //list2 = list2.next;
        //list2 = new ListNode(4);
        //list2 = list2.next;
        //list2 = new ListNode(6);
        list2.next = new ListNode(4);
        list2.next.next = new ListNode(6);

        ListNode merge = Solution6.Merge(list1, list2);

        showList(merge);
    }

    private static void showList(ListNode merge) {
        while (merge != null) {
            System.out.print(merge.val);
            if (merge.next != null) {
                System.out.print("——");
            }
            merge = merge.next;
        }
    }
}

【算法】解题总结:剑指Offer 6 旋转数组的最小数字、剑指Offer 16 合并两个排序的链表_第2张图片

你可能感兴趣的:(数据结构与算法,链表,算法,java,数据结构)