Java 算法 随笔

最大连续子序列和问题

即计算出给定无序整数(可能有负值)集合A1,A2….An,求其子序列中最大值的问题

示例输入和结果:

input: 6 -7 0 -6 5 -1 3 -3 3 -8 
output: 7

最优算法

/**
     * 算法4 最优解 时间复杂度为O(N) 
     * 
     * 利用最大子序列的首尾值肯定大于零的特性
     * 
     * @param a
     * @return
     */
    public int MaxSubSumAlgorithm4(int[] a) {
        int maxSum = 0;//最终最大子序列和
        int tempSum = 0;//临时最大子序列和

        int size = a.length;

        for(int i=0; i< size; i++) {
            tempSum += a[i];

            if(tempSum > maxSum) {
                maxSum = tempSum;
            } else if(tempSum < 0) {
                tempSum = 0;
            }
        }

        return maxSum;
    }

经测试发现该算法在输入大小为100000数量级时,耗时3ms左右。

分治算法

    public int maxSubArrayHelperFunction(int A[], int left, int right) {
        if(right == left) return A[left];
        int middle = (left+right)/2;
        int leftans = maxSubArrayHelperFunction(A, left, middle);
        int rightans = maxSubArrayHelperFunction(A, middle+1, right);
        int leftmax = A[middle];
        int rightmax = A[middle+1];
        int temp = 0;
        for(int i=middle;i>=left;i--) {
            temp += A[i];
            if(temp > leftmax) leftmax = temp;
        }
        temp = 0;
        for(int i=middle+1;i<=right;i++) {
            temp += A[i];
            if(temp > rightmax) rightmax = temp;
        }
        return Math.max(Math.max(leftans, rightans),leftmax+rightmax);
    }

个人认为此算法的时间复杂度大于O(N)呢???

堆排序

                                               A
                                            B     C
                                        D    E  F   G

堆是一颗被完全填满的二叉树,有可能的例外是在底层,底层上的元素从左到右填入。根据不同的堆序性质,可以分为升序堆(根节点最小)和降序堆(根节点最大)。堆排序的时间复杂度为O(N logN)。

下面以降序堆(每一个节点都大于它的子节点,根节点为最大节点)为例,讲解堆排序。由堆序性质可知,最大值肯定是根节点,我们将根节点跟堆得末尾节点进行交换后,最大值放在逻辑上不属于堆得末尾位置。然后将更新后的根节点依次下虑,直到找到合适的位置,此时堆序性质依然保持。然后重复上述步骤,即可得到一个升序的数组。

下虑算法为:

/**
     * 堆排序的内部方法
     * 用来删除最大值并重新构建堆
     * 
     * @param heapSet   堆集合
     * @param i         要降序的位置
     * @param heapSize  堆得逻辑大小
     */
    private void percDown(int[] heapSet, int i, int heapSize) {
        //子节点的位置
        int child;
        //将要降序的位置的值
        int temp;
        for(temp = heapSet[i]; leftChild(i) < heapSize; i = child) {
            //左子节点位置
            child = leftChild(i);
            //如果左子节点小于右子节点,child加1
            if(child < heapSize && child + 1 < heapSize && heapSet[child] < heapSet[child + 1]) {
                child ++;
            }

            //父节点跟较大的子节点比较,如果子节点较大,将其职赋予父节点,否则找到合适位置,结束循环
            if(child < heapSize && temp < heapSet[child]) {
                heapSet[i] = heapSet[child];
            } else {
                break;
            }
        }
        //i为合适配置,将当初需要降序的值放在此位置
        heapSet[i] = temp;
    }

第一步:根据无序数组构造一个降序堆。
根据堆的特性:底层节点(没有子节点的节点)的数量为N/2。
可以将前N/2的节点挨个进行下虑操作,即可将无序数组构造成堆。

/**
     * 将无序数组构建成堆数组
     * 
     * @param originSet 原始无序数组
     * @return          堆数组
     */
    public int[] buildHeap(int[] originSet) {
        int length = originSet.length;
        for(int i = length / 2 -1; i >= 0; i--) {
            percDown(originSet, i, length);
        }
        return originSet;
    }

第二步:循环删除根节点

public void heapSort(int[] originSet) {
        int[] heapSet = buildHeap(originSet);
        int length = heapSet.length;
        for(int i = length - 1; i > 0; i--) {
            swapReferences(originSet, 0, i);
            percDown(heapSet, 0, i);
        }
    }

其他辅助方法:

/**
     * 找到节点的左子节点位置
     * @param parent  父节点位置
     * @return        左子节点位置
     */
    private int leftChild(int parent) {
        return 2 * parent +1;
    }


private void swapReferences(int[] heapSet, int position1, int position2) {
        int temp = heapSet[position1];
        heapSet[position1] = heapSet[position2];
        heapSet[position2] = temp;
    }

测试程序示例:

public static void main(String[] args) {
        int[] originSet = {4, 5, 7, 1, 2, 9, 10, 8, 3, 6};
        HeapSort heapSort = new HeapSort();
        int[] heapSet = heapSort.buildHeap(originSet);
        String heap = "";
        for(int i=0; i< heapSet.length; i++) {
            heap += " " + heapSet[i];
        }
        System.out.println("堆结构数组---->" + heap);

        heapSort.heapSort(heapSet);

        String sort = "";
        for(int i=0; i< heapSet.length; i++) {
            sort += " " + heapSet[i];
        }
        System.out.println("排序后数组---->" + sort);
    }

程序输出为:
堆结构数组—-> 10 8 9 5 6 4 7 1 3 2
排序后数组—-> 1 2 3 4 5 6 7 8 9 10

插入排序

时间复杂度为O(N^2)

public void sort(int[] input) {
        if (input.length <= 1) {
            return;
        }
        int startPos = 1;
        int length = input.length;
        for (int i = startPos; i < length; i++) {
            int temp = input[i];
            for (int j = i - 1; j >= 0; j--) {

                if (j > 0 && temp > input[j - 1]) {
                    input[j] = input[j - 1];
                } else {
                    input[j] = temp;
                    break;
                }
            }
        }
    }

测试代码:

    public static void main(String[] args) {
        InsertSort insertSort = new InsertSort();
        int[] data = {1, 2, 7, 3, 4, 5, 10, 2, 3, 8, 11, 6, 9};
        insertSort.sort(data);

        String sorted = "";
        for (int i = 0; i < data.length; i++) {
            sorted += " " + data[i];
        }
        System.out.println("排序后的结果--->" + sorted);
    }

结果输出:
排序后的结果—> 11 10 9 8 7 6 5 4 3 3 2 2 9

快速排序

排序思想
1、取待排序数组中值(常用方法是取数组首、尾、中间的值作比较,并将中值交换到数组倒数第二的位置,最后返回中值。)
2、新建两个数字下标索引,分别指向0和N-2,然后一次与中值进行对比,当两个下标都停止时,交互两下标指向的值,直到两下标相交。
3、当下标相交时,小于中值的部分在下标左侧,大于中值的部分在下标右侧,再递归重复上述操作。

时间复杂度
O(NlogN)

注:当数组较小时,使用简单排序更有效。

Java编码核心实现如下:

public void sort(int[] input, int startIndex, int endIndex) {
        //参数检查
        if (input == null || input.length <= 1 || endIndex <= startIndex) {
            return;
        }

        //数组长度小于10的时候,使用插入排序
        if (startIndex + mUseInsertSortLimit > endIndex) {
            insertSort.ascendingSort(input, startIndex, endIndex);
            return;
        }

        int medianValue = median3(input, startIndex, endIndex);

        //开始分割
        int left = startIndex;
        int right = endIndex - 1;
        for (; ; ) {
            while (input[++left] < medianValue) {
            }
            while (input[--right] > medianValue) {
            }
            //升序排序,大值在左边
            if (left < right) {
                swapReference(input, left, right);
            } else {
                break;
            }
        }
        //因为在寻找中值将中值交换到了endIndex-1的位置
        //所以在一轮比较过后,要将中值交换回到下标相交位置
        swapReference(input, left, endIndex - 1);

        //递归调用sort方法,再对小于中值和大于中值的部分进行排序
        sort(input, startIndex, left - 1);
        sort(input, left + 1, endIndex);
    }

寻找中值和位置交换方法如下:

//交换数组位置
    private void swapReference(int[] a, int left, int right) {
        int temp = a[left];
        a[left] = a[right];
        a[right] = temp;
    }

    //取数组中值,并将中值转移到倒数第二位置
    private int median3(int[] a, int left, int right) {
        int center = (left + right) / 2;
        if (a[left] > a[center]) {
            swapReference(a, left, center);
        }
        if (a[center] > a[right]) {
            swapReference(a, center, right);
        }
        if (a[left] > a[right]) {
            swapReference(a, left, right);
        }
        swapReference(a, center, right - 1);
        return a[right - 1];
    }

二分查找算法

Java语言实现

public static final int INVALID_POSITION = -1;
/**
     * 二分查找算法
     *
     * @param input       有序数组
     * @param startIndex  检索开始位置
     * @param endIndex    检索结束位置
     * @param targetValue 查找的关键字
     * @return 关键字所在位置 -1 代表没有找到
     */
    public int search(int[] input, int startIndex, int endIndex, int targetValue) {
        //参数检查
        if (input == null || input.length == 0 || startIndex > endIndex || startIndex < 0 || endIndex < 0 || startIndex >= input.length || endIndex
                >= input.length) {
            return INVALID_POSITION;
        }

        int binaryIndex = (startIndex + endIndex) / 2;
        if (input[binaryIndex] == targetValue) {
            return binaryIndex;
        }

        if (startIndex == binaryIndex || endIndex == binaryIndex) {
            return INVALID_POSITION;
        }

        if (targetValue > input[binaryIndex]) {
            startIndex = binaryIndex + 1;
        } else {
            endIndex = binaryIndex - 1;
        }

        return search(input, startIndex, endIndex, targetValue);

    }

无序数组寻找第k大的数

算法思想
根据快速排序思想(升序),每一趟排序后中值左边的值都小于右边的值,这时只需要判断K点在左边还是在右边。当然也有可能等于中值位置,那么中值位置就是要寻找的K。否则,再次按照快速排序的步骤再次排序,直到找到K点。
Java语言实现

    public static void swap(int[] a, int i, int j){
        int temp = a[i];
        a[i] =  a[j];
        a[j] = temp;
    }

    public static int partition(int[] arr, int low, int high){
        int pivot = arr[low];
        int i= low, j = high;
        while(i<=j){
            while(i<=j && arr[i]<=pivot)i++;
            while(i<=j && arr[j]>=pivot)j--;
            swap(arr,i,j);
        }
        swap(arr,low,j);
        return j;
    }
    //第k大的数,如果数组长度奇数,则k=(1+n)/2, 否则k=n/2
    public static int findMedian(int[] arr, int k, int low, int high){
        if(k >high -low +1) return -1;
        int pos = partition(arr,low, high);
        //如果寻找点在中值点右侧
        if(pos - low < k -1){
            return findMedian(arr, k-pos-1, pos+1, high);
            //如果寻找点等于中值点
        }else if(pos - low == k-1){
            return arr[pos];
        }else {
        //如果寻找点在中值点左侧
            return findMedian(arr, k, low, pos-1);
        }
    }

测试代码

//找出无序数组中位数,即有序数组中间位置的数
public static void main(String[] args) {
        int[] arr= {1, 1, 1, 1, 1, 1, 2, 3};
        int res = 0;
        if(arr.length%2 ==1){
            res = findMedian(arr, (arr.length+1)/2, 0, arr.length-1);
        }else{
            res = findMedian(arr, arr.length/2, 0, arr.length-1);
        }
        System.out.println(res);
    }

输出结果
1

查找单链表中是否存在环

算法思想
新建两个指针p和q,从头节点出发,每次迭代p走一个节点,q走两个节点,当两个指向相同节点时说明存在环,否则不存在环。

关键概念
在长度为n的单链表中,经过i次迭代,当p和q相遇时,一定满足 i = 2i(mod n)。也就是说,q走过的节点数是p走过的节点数加上X倍数的链表长度。

迷惑问题
当p和q相遇时,p一定没有走完环中的节点?
假设当p刚进入环时,q距离p为k。环长n。
经过i次迭代后,q指针和p指针相遇。
此时 i = (2i + k) mod n
推算出 (i + k) mod n = 0
所以 i +k <= n
可以得出结论:当p指针进入环后,在小于等于环长度的迭代次数内,一定能和q指针相遇。

Java代码实现

//模拟链表实现
static class Node {
        public String name;
        public Node next;

        public Node(String name) {
            this.name = name;
        }
    }

/**
     * 第一次环中相遇点
     *
     * @param head 头结点
     * @return
     */
    public Node firstMeetNodeInCycle(Node head) {
        if (head == null || head.next == null) {
            return null;
        }

        Node slow = head;
        Node fast = head;

        while ((slow = slow.next) != null && (fast = fast.next.next) != null) {
            if (slow == fast) {
                return slow;
            }
        }
        return null;
    }
    /**
     * 环中第一个节点
     * @param head
     * @return
     */
    public Node fistNodeInCycle(Node head) {
        Node meetNode = firstMeetNodeInCycle(head);
        if(meetNode != null) {
            while(head != meetNode) {
                head = head.next;
                meetNode = meetNode.next;
            }
            return head;
        }
        return null;
    }

测试代码

public static void main(String[] args) {
        Node head = new Node("node0");
        Node n1 = new Node("node1");
        Node n2 = new Node("node2");
        Node n3 = new Node("node3");
        Node n4 = new Node("node4");
        Node n5 = new Node("node5");
        Node n6 = new Node("node6");
        Node n7 = new Node("node7");
        head.next = n1;
        n1.next = n2;
        n2.next = n3;
        n3.next = n4;
        n4.next = n5;
        n5.next = n6;
        n6.next = n7;
        n7.next = n3;

        LinkedListCycle linkedListCycle = new LinkedListCycle();

        boolean hasCycle = linkedListCycle.hasCycle(head);

        System.out.println("单链表中是否存在环 = " + hasCycle);

        Node meetNode = linkedListCycle.firstMeetNodeInCycle(head);

        System.out.println("环中第一次相遇节点名称 = " + meetNode.name);

        Node fistNode = linkedListCycle.fistNodeInCycle(head);

        System.out.println("环中第一个节点名称 = " + fistNode.name);
    }

算法时间复杂度O(n) 空间复杂度O(1)

相关问题扩展
1、找出环中第一个节点
2、链表头节点与环中第一个节点的距离
3、如何判断两个单链表是否相交

两个有序数组求中位数

更一般的问题是 两个有序数组求第K大的值

最优算法思想
利用数组有序的特性,比较两个数组A和B的 k/2 处的值,即比较A[k/2 - 1]和B[k/2 - 1]。如果两者相等,则此处即为第K大的值。
如果A[k/2 - 1] 小于 B[k/2 -1],说明第K大的值肯定不在A[0] - A[k/2 -1] 之间,则可以删除数组A中A[0] - A[k/2 -1]这个区间的值。之后就是查找新数组A和数组B中第k/2大的数。
反之,如果A[k/2 -1] 大于 B[k/2 -1],则第K大的值肯定不在B[0] - B[k/2 - 1]区间内。

复杂度
时间复杂度为O(log(m+n))
空间复杂度为O(log(m+n))

Java语言实现

    /**
     * 执行查找
     * @param a         第一个数组
     * @param startA    查询起始位置
     * @param endA      查询结束位置
     * @param b         第二个数组
     * @param startB    查询起始位置
     * @param endB      查询结束位置
     * @param pos       第pos大的数
     * @return          第pos大的数
     */
    public int find(int[] a, int startA, int endA, int[] b, int startB, int endB, int pos) {

        if (a == null || b == null || pos < 0) {
            throw new IllegalArgumentException("params are illegal");
        }

        int lenA = endA - startA + 1;
        int lenB = endB - startB + 1;

        //保证a的长度小于b
        if (lenA > lenB) {
            return find(b, startB, endB, a, startA, endA, pos);
        }

        //如果小数组的长度为0,pos位置肯定在b数组中
        if (lenA == 0) {
            return b[pos - 1];
        }

        //如果找第一个大的数,返回两个数组0位置出较小那个
        if (pos == 1) {
            return Math.min(a[startA], b[startB]);
        }

        //将数组分成两部分
        int tempPosA = Math.min((pos / 2), lenA);
        int tempPosB = pos - tempPosA;
        //较小的部分可以忽略,继续查找剩下部分,当然查找位置也要减去忽略部分的大小
        if (a[startA + tempPosA - 1] < b[startB + tempPosB - 1]) {
            return find(a, startA + tempPosA, endA, b, startB, endB, pos - tempPosA);
        } else if (a[startA + tempPosA - 1] > b[startB + tempPosB - 1]) {
            return find(a, startA, endA, b, startB + tempPosB, endB, pos - tempPosB);
        } else {
            return a[startA + tempPosA - 1];
        }
    }

测试代码

    public static void main(String[] args) {
        int[] a = {1, 3, 5, 7, 9, 11};
        int[] b = {2, 4, 6, 8, 10};
        MedianOfTwoSortedArrays medianOfTwoSortedArrays = new MedianOfTwoSortedArrays();
        int result = medianOfTwoSortedArrays.find(a, 0, a.length - 1, b, 0, b.length - 1, (a.length + b.length + 1) / 2);

        System.out.println("两个有序数组中位数---->" + result);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值