最大连续子序列和问题
即计算出给定无序整数(可能有负值)集合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);
}