目录
- 第一题:复杂链表的复制
- 第二题:二叉搜索树与双向链表
- 第三题:字符串的排列
- 第四题:数组中出现次数超过一半的数字
- 第五题:最小的K个数
- 第六题:连续子数组的最大和
第一题:复杂链表的复制
题目链接
题目
解析
这个题目和LeetCode-138.Copy List with Random Pointer完全一样,那篇博客有详细的解释,不再赘述。
方法一: 使用HashMap保存
public RandomListNode Clone(RandomListNode pHead){
HashMap<RandomListNode,RandomListNode> map = new HashMap<>();
RandomListNode cur = pHead;
while(cur != null){
map.put(cur,new RandomListNode(cur.label));
cur = cur.next;
}
cur = pHead;
while(cur != null){
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
return map.get(pHead);
}
方法二: 方法一的另一种写法
public RandomListNode Clone(RandomListNode pHead){
if(pHead == null)
return null;
HashMap<RandomListNode,RandomListNode>map = new HashMap<>();
RandomListNode copyHead = new RandomListNode(pHead.label);
map.put(pHead,copyHead);
RandomListNode cur = pHead,copyCur = copyHead;
while(cur != null){
if(cur.next != null)
copyCur.next = new RandomListNode(cur.next.label);
map.put(cur.next,copyCur.next);
cur = cur.next;
copyCur = copyCur.next;
}
cur = pHead;
while(cur != null){
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
return copyHead;
}
方法二的递归写法:
public RandomListNode Clone(RandomListNode pHead){
if(pHead == null)
return null;
HashMap<RandomListNode,RandomListNode>map = new HashMap<>();
RandomListNode copyHead = process(pHead,map);
RandomListNode cur = pHead;
while(cur != null){
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
return copyHead;
}
// 宏观来看: 就是返回拷贝以node为头结点的链表的拷贝 以及next的拷贝
private RandomListNode process(RandomListNode node, HashMap<RandomListNode,RandomListNode>map){
if(node == null){
return null;
}
RandomListNode copyNode = new RandomListNode(node.label);
map.put(node,copyNode);
copyNode.next = process(node.next,map);
return copyNode;
}
O(1)空间的写法:
public RandomListNode Clone(RandomListNode pHead){
if(pHead == null)
return null;
RandomListNode cur = pHead,next = null;
//先拷贝一份原来的链表
while(cur != null){
next = cur.next; //先存着之前的next
cur.next = new RandomListNode(cur.label);
cur.next.next = next;
cur = next;
}
//复制结点的random指针
cur = pHead;
RandomListNode copyCur = null;
while(cur != null){
next = cur.next.next; //保存原来链表中的下一个
copyCur = cur.next; //复制链表的cur
copyCur.random = cur.random != null ? cur.random.next : null;
cur = next;
}
//拆开两个链表
RandomListNode copyHead = pHead.next;
cur = pHead;
while(cur != null){
next = cur.next.next;
copyCur = cur.next;
cur.next = next;
copyCur.next = next != null ? next.next : null;
cur = next;
}
return copyHead;
}
第二题:二叉搜索树与双向链表
题目链接
题目
转换示例:
解析
- 方法一: 改进中序非递归遍历(注意使用pre保存上一个访问的结点),这个只要会非递归的中序遍历,其实是最简单的方法,每次记录上一个访问的结点,然后每次当前结点为空的时候,就设置pre和cur的关系即可;
- 方法二(没有使用pre结点): 中序递归的时候,每次递归完之后,返回的是左右孩子都变成了双向链表,并且返回的是变成双向链表的根节点,然后要做的操作就是: a. 找到左边返回的树中的最右边的结点;b.找到右边返回的树中的最左边的结点;c.将这两个结点和当前的根节点的关系连接起来;
例如:
- 方法三(递归+pre结点): 这个也是使用pre结点,不过是将非递归改成递归, 但是要注意Java中参数传递的是引用类型,所以要使用数组或者全局变量来记录pre;
- 方法四(递归+连接双向链表): 这个是左神的解法,是递归的改进版本,上面的方法在递归完成之后,需要向左不断的循环,找到最左边的结点,然后返回,其实可以省掉这个,在递归的时候,设置好返回的链表的左右两端的结点,注意分情况判断;
看下书上的解释:
(1)
(2)
方法一: 改进的非递归,关于非递归中序,可以看这个博客
public TreeNode Convert(TreeNode pRootOfTree) {// 返回排序的双向链表的头结点
if(pRootOfTree == null)
return null;
Stack<TreeNode>s = new Stack<>();
TreeNode pre = null,cur = pRootOfTree,res = null;
boolean isFirst = true;
while(!s.isEmpty() || cur != null){
if(cur != null){
s.push(cur);
cur = cur.left;
}else {
cur = s.pop();
if(isFirst){
isFirst = false;
res = cur;
pre = cur;
}else {
pre.right = cur;
cur.left = pre;
pre = cur;
}
cur = cur.right; //当前结点向右
}
}
return res;
}
方法二: 递归的(没有使用pre结点):
public TreeNode Convert(TreeNode pRootOfTree) {// 返回排序的双向链表的头结点
if(pRootOfTree == null)
return null;
TreeNode last = convert(pRootOfTree);//返回的还是根结点
while(last != null && last.left != null)
last = last.left;
return last;
}
private TreeNode convert(TreeNode root){
if(root == null)
return null;
TreeNode L = convert(root.left);
TreeNode R = convert(root.right);
//将左子树的最后一个结点(最大结点)和根节点链接起来
if(L != null){
while(L.right != null){
L = L.right;
}
L.right = root;
root.left = L;
}
//将右子树的最小的结点和根结点链接起来
if(R != null){
while(R.left != null){
R = R.left;
}
R.left = root;
root.right = R;
}
return root;//返回的是链接之后的根节点
}
方法三(递归+pre结点)
注意这里和C++的不同,Java传递的是引用,所以这里使用数组来保存或者使用一个全局变量。
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return null;
TreeNode[] pre = new TreeNode[1];
convert(pRootOfTree,pre);//返回的pre 是双向链表的最后一个结点
TreeNode last = pre[0];
while(last != null && last.left != null)
last = last.left;
return last;
}
private void convert(TreeNode root,TreeNode[] pre){
if(root == null)
return ;
convert(root.left,pre);
root.left = pre[0];
if(pre[0] != null)
pre[0].right = root;
pre[0] = root;
convert(root.right,pre);
}
使用全局变量:
public class Solution {
private TreeNode pre;
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return null;
pre = null;
convert(pRootOfTree);//返回的pre 是双向链表的最后一个结点
while(pre != null && pre.left != null)
pre = pre.left;
return pre;
}
private void convert(TreeNode root){
if(root == null)
return ;
convert(root.left);
root.left = pre;
if(pre != null)
pre.right = root;
pre = root;
convert(root.right);
}
}
方法四(递归+连接双向链表)
不需要最后遍历到第一个结点返回,保持连接的始终是一个双向的链表:
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return null;
TreeNode last = convert(pRootOfTree);
TreeNode res = last.right;
last.right = null;
return res;
}
//功能: 将二叉搜索树转换成一个有序的双向链表,并返回连接之后的最后一个结点
private TreeNode convert(TreeNode root){
if(root == null)
return null;
TreeNode endL = convert(root.left); //左边的结尾的结点
TreeNode endR = convert(root.right); //右边的结尾的结点
TreeNode startL = endL != null ? endL.right : null; //左边开始的结点
TreeNode startR = endR != null ? endR.right : null; //右边开始的结点
//分情况连接这些结点
if(endL != null && endR != null){
//连接根节点的
root.left = endL;
endL.right = root;
root.right = startR;
startR.left = root;
//最后一个指回去
endR.right = startL;
return endR;
}else if(endL != null){//右边为空
root.left = endL;
endL.right = root;
root.right = startL;
return root;
}else if(endR != null){//左边为空
root.right = startR;
startR.left = root;
endR.right = root;
return endR;
}else { //左右都是空
root.right = root;
return root;
}
}
第三题: 字符串的排列
题目链接
题目
解析
四种写法:
这个题目解析可以看下LeetCode46和LeetCode47。
方法一:
import java.util.ArrayList;
import java.util.Collections;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String>res = new ArrayList<>();
if(str != null && str.length() != 0){
process(str.toCharArray(),0,res);
Collections.sort(res);
}
return res;
}
private void process(char[] str,int cur,ArrayList<String>res){
if(cur == str.length-1){
if(!res.contains(String.valueOf(str)))
res.add(String.valueOf(str));
}else for(int i = cur; i < str.length; i++){
swap(str,cur,i);
process(str,cur+1,res);
swap(str,cur,i);
}
}
private void swap(char[] arr,int i,int j){
char t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
方法二:
注意这个需要在process之前对数组进行排序,这里虽然在牛客网上不排序也能通过,但是看下面的例子:
输入str : “ddad”
如果不排序: 输出
[ddad, ddda, dadd, ddda, ddad, addd, ddda, ddad, dadd]
上面的有重复,排序之后,输出:
[addd, dadd, ddad, ddda]
import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String>res = new ArrayList<>();
if(str != null && str.length() != 0){
char[] charArr = str.toCharArray();
Arrays.sort(charArr);//注意这里需要排序,虽然牛客网的可以通过,但是有问题
process(charArr,0,res);
}
return res;
}
private void process(char[] str,int cur,ArrayList<String>res){
char[] newStr = new char[str.length];
for(int i = 0; i < str.length; i++)
newStr[i] = str[i];
if(cur == newStr.length-1){
res.add(String.valueOf(newStr));
}else for(int i = cur; i < newStr.length; i++){
if(cur != i && newStr[cur] == newStr[i])
continue;
swap(newStr,cur,i);
process(newStr,cur+1,res);
//swap(newStr,cur,i);//这里不能交换,不然得不到字典序
}
}
private void swap(char[] arr,int i,int j){
char t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
方法三:
使用HashSet去重
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String>res = new ArrayList<>();
if(str != null && str.length() != 0){
process(str.toCharArray(),0,res);
Collections.sort(res); //这个也是在之后排序
}
return res;
}
//使用Set去重
private void process(char[] str,int cur,ArrayList<String>res){
if(cur == str.length-1){
res.add(String.valueOf(str));
}else {
HashSet<Character>set = new HashSet<>();
for(int i = cur; i < str.length; i++){
if(!set.contains(str[i])){
set.add(str[i]);
swap(str,cur,i);
process(str,cur+1,res);
swap(str,cur,i);
}
}
}
}
private void swap(char[] arr,int i,int j){
char t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
方法四: 深搜
import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String>res = new ArrayList<>();
if(str != null && str.length() != 0){
char[] charArr = str.toCharArray();
Arrays.sort(charArr);//保证相邻的元素在一块
dfs(res,new ArrayList<>(),charArr,new boolean[str.length()]);
}
return res;
}
private void dfs(ArrayList<String>res,ArrayList<Character>tmp,char[] str,boolean[] used){
if(tmp.size() == str.length){
String s = "";
for(Character c : tmp) s += c;
res.add(s);
}else for(int i = 0; i < str.length; i++){
if(used[i] || (i > 0 && !used[i-1] && str[i] == str[i-1]))//去重
continue;
used[i] = true;
tmp.add(str[i]);
dfs(res,tmp,str,used);
used[i] = false;
tmp.remove(tmp.size()-1);
}
}
private void swap(char[] str,int i,int j){
char t = str[i];
str[i] = str[j];
str[j] = t;
}
}
第四题:数组中出现次数超过一半的数字
题目链接
题目
解析
这个题目和LeetCode-215. Kth Largest Element in an Array和像,有兴趣的可以看看那篇博客。
三种方法:
- 方法一: 直接使用额外的空间HashMap保存出现的次数;
- 方法二: 利用快排的partition过程;
- 方法三: 巧妙的利用数组的特点的O(n)算法;
代码如下:
方法一: 使用map来保存每个元素出现的次数,只要某个元素次数超过array.length/2就返回,很简单;
import java.util.HashMap;
public class Solution {
public int MoreThanHalfNum_Solution(int[] array) {
if(array == null || array.length == 0)
return 0;
if(array.length == 1)
return array[0];
HashMap<Integer,Integer>map = new HashMap<>();
for(int i = 0; i < array.length; i++){
if(map.containsKey(array[i])){
map.put(array[i],map.get(array[i]) + 1);
if(map.get(array[i]) > array.length / 2)
return array[i];
}else {
map.put(array[i],1);
}
}
return 0;
}
}
方法二:
使用类似快速排序的思想:
- 对于次数超过一半的数字,则数组中的中位数一定是该数字,(如果数组中真的存在次数超过一半的数字),时间复杂度为O(n);
关于快速排序可以看这篇博客
- 注意这里不是三路快排(返回的不是等于区域的两个下标),而是<=key的在[L,border]之间,>key的在[border+1,R]之间,而arr[border] = key(划分数),因为我模仿的是快排,最后交换了>区域的最后一个数和arr[more]和划分数arr[R];
public class Solution {
//对于次数超过一半的数字,则数组中的中位数一定是该数字,(如果数组中真的存在次数超过一半的数字),时间复杂度为O(n)
public int MoreThanHalfNum_Solution(int[] array) {
if(array.length == 0 || array == null)
return 0;
if(array.length == 1)
return array[0];
int L = 0,R = array.length - 1;
int border = partition(array,L,R);
int mid = array.length / 2; //中间位置
while(border != mid){
if(mid < border){//mid在左边,去左边找
R = border - 1;//更新R // array[border]那个一定等于那个划分数 我模仿的是三路快排,最后swap(R,more)
border = partition(array,L,R);
}else {
L = border + 1;
border = partition(array,L,R);
}
}
int res = array[mid];
int times = 0;
for(int i = 0; i < array.length; i++)
if(res == array[i])
times++;
if(times * 2 <= array.length)
return 0;
return res;
}
private int partition(int[] arr,int L,int R){
int less = L-1;
int more = R;
swap(arr, L + (int)(Math.random() * (R-L+1)) ,R);//随机选取一个数 用来和arr[R]划分
int key = arr[R];//选取arr[R]作为划分数
int cur = L;
while( cur < more ){
if(arr[cur] < key){
swap(arr,++less,cur++); //把这个比num小的数放到小于区域的下一个,并且把小于区域扩大一个单位
}else if(arr[cur] > key){
//把这个比num大的数放到大于去余的下一个,并且把大于区域扩大一个单位
//同时,因为从大于区域拿过来的数是未知的,所以不能cur++ 还要再次判断一下arr[cur]
swap(arr,--more,cur);
}else{//否则的话就直接移动
cur++;
}
}
swap(arr,more,R); //把最后那个数(arr[R](划分数))放到中间
return more;//返回的是<=区域的右边界
}
private void swap(int[] arr,int i,int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
方法三:
- 如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多;
- 在遍历数组时保存两个值:一个是数组中每次遍历的候选值cand,另一个当前候选值的次数times;
- 遍历时,若当前值它与之前保存的候选值cand相同,则次数tims加1,否则次数减1;若次数为0,则保存下一个新的数字cand,并将新的次数times置为1;
- 遍历结束后,所保存的数字(剩下的)即为所求。当然还需要判断它是否符合条件(因为有可能没有数字次数>N/2);
看下书上的解释:
public class Solution {
public int MoreThanHalfNum_Solution(int[] array) {
if(array.length == 0 || array == null)
return 0;
if(array.length == 1)
return array[0];
int cand = 0,times = 0;
for(int i = 0; i < array.length; i++){
if(times == 0){
cand = array[i];
times = 1;
}else if(array[i] == cand){//又遇到一个同样的,累加
times++;
}else {// times != 0 && array[i] != res
times--;
}
}
// 最后一定要检验,不一定就是res
times = 0;
for(int i = 0; i < array.length; i++)
if(array[i] == cand)
times++;
if(times * 2 > array.length)
return cand;
return 0;
}
}
这个题目有两个变形:
- 求数组中>n/3的次数的数(最多两个);
- 求数组中>n/k的次数的数;
这类问题统称为摩尔投票问题:
第一题解析
第一题题目来自LeetCode229-Majority Element II,求出数组中>n/3次数的数。
- 和>n/2次数的数解题方法很相似,>n/2的候选人cand只有一个,统计次数只有一个times;
- 而>n/3次数的数解题是设置两个候选人cand1和cand2,并且设计两个统计次数count1和count2,按照类似的方法统计;
- 按照投票的说法,大于n/3次数的解题方法是: 先选出两个候选人cand1,cand2,如果投cand1,则cand1的票数count1++,如果投cand2,则cand2的票数count2++,如果既不投cand1,也不投cand2,那么检查此时是否cand1和cand2候选人的票数是否已经为0,如果为0,则需要更换新的候选人;如果都不为0,则cand1和cand2的票数都要减一;当然最后也需要看看是否两个候选人的票数超过nums.length / 3;
LeetCode229题解:
class Solution {
public List<Integer> majorityElement(int[] nums) {
List<Integer>res = new ArrayList<>();
if(nums == null || nums.length == 0)
return res;
if(nums.length == 1){
res.add(nums[0]);
return res;
}
int cand1 = 0,cand2 = 0;
int count1 = 0,count2 = 0;
for(int i = 0; i < nums.length; i++){
if(nums[i] == cand1){
count1++;
}else if(nums[i] == cand2){
count2++;
}else if(count1 == 0){
cand1 = nums[i];
count1 = 1;
}else if(count2 == 0){
cand2 = nums[i];
count2 = 1;
}else { // count1 != 0 && nums[i] != cand1 && count2 != 0 && cand2 != nums[i]
count1--;
count2--;
}
}
//此时选出了两个候选人,需要检查
count1 = 0; count2 = 0;
for(int i = 0; i < nums.length; i++){
if(nums[i] == cand1){
count1++;
}else if(nums[i] == cand2){
count2++;
}
}
if(count1 > nums.length/3)
res.add(cand1);
if(count2 > nums.length/3)
res.add(cand2);
return res;
}
}
第二题解析
上面是超过n/2、n/3次的,如果需要求所有超过n/k次的,则需要时间O(n*k),空间O(k);
会了>n/2的和>n/3的,则>n/k的也是类推的,使用一个map来保存k个候选人即可:
这里使用LeetCode229来测试我们的求>n/k的程序:
import java.util.Map.Entry;
class Solution {
public List<Integer> majorityElement(int[] nums) {
return printKMajority(nums,3);
}
//找出数组中出现次数 > N/K的, 创建空间为O(k)的候选人的集合
public List<Integer> printKMajority(int[] arr,int k){
ArrayList<Integer>res = new ArrayList<>();
if(k < 2)
return res;
HashMap<Integer,Integer>cands = new HashMap<>();
for(int i = 0; i < arr.length; i++){
if(cands.containsKey(arr[i])){//在候选人的集合中有这个候选人了
cands.put(arr[i],cands.get(arr[i]) + 1);//给他的票数+1
}else { //与所有的候选人都不同
//候选人的集合已经满了(当前是第K个),要把所有的候选人的票数减一,如果某些人的票数是1就要移除这个候选人
if(cands.size() == k-1){
ArrayList<Integer>removeList = new ArrayList<>();
for(Entry<Integer,Integer>entry : cands.entrySet()){
Integer key = entry.getKey();
Integer value = entry.getValue();
if(value == 1){
removeList.add(key);
}else {
cands.put(key,value-1);
}
}
//删除那些value = 1的候选人
for(Integer removeKey : removeList)
cands.remove(removeKey);
}else { //没有满,把这个加入候选人的集合
cands.put(arr[i],1);
}
}
}
//检查候选人是否真的满足条件
for(Entry<Integer,Integer>entry : cands.entrySet()){
Integer key = entry.getKey();
int sum = 0;
for(int i = 0; i < arr.length; i++){
if(arr[i] == key)
sum++;
}
if(sum > arr.length/k)
res.add(key);
}
return res;
}
}
第五题:最小的k个数
题目链接
题目
解析
大体有两种思路:
- 使用快速排序类似partition的过程,概率复杂度可以做到O(n)(BFPRT算法可以稳定做到O(N)),和快排不同,这个递归的时候只需要去某一边,但是快排两边都要去;这种方法修改了原数组;
- 使用堆排序,使用最大堆维护K个数(堆顶最大),一直保持堆中有K个最小的数,时间复杂度N*logK,也可以使用最小堆来做;
思路一: 使用类似快排的partition
先用master公式看看时间复杂度:
这种思路就是不停的划分,直到我们的border(分界点) = K-1,这时,<=K-1位置的数就是最小的K个数,每次只需要往一边:
- 如果我们选的划分数很好(在中间): 则T(N) = T(N/2) + O(N) (注意不是2*T(N/2),因为只需要往某一边走),即时间复杂度为: O(N);
- 如果我们选的划分数很差(极端) : 则T(N) = T(N-1) + O(N) 时间复杂度为: O(N2);
- 但是概率平均复杂度为O(N);
代码如下:
非递归的 :
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer>res = new ArrayList<>();
if(input == null || k <= 0 || k > input.length)
return res;
int L = 0,R = input.length - 1;
int border = partition(input,L,R);
while(border != k-1){//注意第K小的就是划分到k-1(下标)个
if(k-1 < border){
R = border - 1;
border = partition(input,L,R);
}else {
L = border + 1;
border = partition(input,L,R);
}
}
for(int i = 0; i < k; i++)
res.add(input[i]);
return res;
}
private int partition(int[] arr,int L,int R){
int less = L-1;
int more = R;
swap(arr, L + (int)(Math.random() * (R-L+1)) ,R);//随机选取一个数 用来和arr[R]划分
int key = arr[R];//选取arr[R]作为划分数
int cur = L;
while( cur < more ){
if(arr[cur] < key){
swap(arr,++less,cur++); //把这个比num小的数放到小于区域的下一个,并且把小于区域扩大一个单位
}else if(arr[cur] > key){
//把这个比num大的数放到大于去余的下一个,并且把大于区域扩大一个单位
//同时,因为从大于区域拿过来的数是未知的,所以不能cur++ 还要再次判断一下arr[cur]
swap(arr,--more,cur);
}else{//否则的话就直接移动
cur++;
}
}
swap(arr,more,R);//把最后那个数(arr[R](划分数))放到中间
return more; //返回的是<=区域的右边界
}
private void swap(int[] arr,int i,int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
递归的:
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer>res = new ArrayList<>();
if(input == null || k <= 0 || k > input.length)
return res;
process(input,0,input.length-1,k);
for(int i = 0; i < k; i++)
res.add(input[i]);
return res;
}
private void process(int[] arr,int L,int R,int k){
int border = partition(arr,L,R);
if(k-1 == border)//划分结束 可以返回退出了
return;
if(k-1 < border){
process(arr,L,border-1,k);
}else {
process(arr,border+1,R,k);
}
}
private int partition(int[] arr,int L,int R){
int less = L-1;
int more = R;
swap(arr, L + (int)(Math.random() * (R-L+1)) ,R);//随机选取一个数 用来和arr[R]划分
int key = arr[R];//选取arr[R]作为划分数
int cur = L;
while( cur < more ){
if(arr[cur] < key){
swap(arr,++less,cur++); //把这个比num小的数放到小于区域的下一个,并且把小于区域扩大一个单位
}else if(arr[cur] > key){
//把这个比num大的数放到大于去余的下一个,并且把大于区域扩大一个单位
//同时,因为从大于区域拿过来的数是未知的,所以不能cur++ 还要再次判断一下arr[cur]
swap(arr,--more,cur);
}else{//否则的话就直接移动
cur++;
}
}
swap(arr,more,R);//把最后那个数(arr[R](划分数))放到中间
return more; //返回的是<=区域的右边界
}
private void swap(int[] arr,int i,int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
思路二: 使用最大堆
一: 使用PriorityQueue
import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer>res = new ArrayList<>();
if(input == null || k <= 0 || k > input.length )
return res;
PriorityQueue<Integer>maxHeap = new PriorityQueue<>(//维护了一个最大堆(堆顶是最大的)
new Comparator<Integer>(){
@Override
public int compare(Integer o1,Integer o2){
return o1 < o2 ? 1 : (o1 == o2 ? 0 : -1);
//return -o1.compareTo(o2);
}
}
);
for(int i = 0; i < input.length; i++){
if(maxHeap.size() < k){//不足k个数,直接加入堆
maxHeap.add(input[i]);
}else if(input[i] < maxHeap.peek()){
maxHeap.poll();
maxHeap.add(input[i]);
}
}
for(Integer item : maxHeap)
res.add(item);
return res;
}
}
二: 手写一个大根堆
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer>res = new ArrayList<>();
if(input == null || k <= 0 || k > input.length)
return res;
int[] kHeap = new int[k];
for(int i = 0; i < k; i++) // 先用k个数建成一个最大堆
siftUp(kHeap,input[i],i);
for(int i = k; i < input.length; i++){
if(input[i] < kHeap[0]){
kHeap[0] = input[i];
siftDown(kHeap,0,k);
}
}
for(int i = 0; i < k; i++)
res.add(kHeap[i]);
return res;
}
//非递归,上浮 //这是最大堆
private void siftUp(int[] arr,int num,int i){
arr[i] = num;
while(arr[i] > arr[(i-1)/2]){
swap(arr,i,(i-1)/2);
i = (i-1)/2;
}
}
//非递归调整 下沉 //这是最大堆
private void siftDown(int[] arr,int i,int heapSize){
int L = 2 * i + 1;
while( L < heapSize){
int maxIdx = L+1 < heapSize && arr[L+1] > arr[L] ? L + 1 : L;//选出左右孩子中最大的
maxIdx = arr[i] > arr[maxIdx] ? i : maxIdx;
if(maxIdx == i)
break;
swap(arr,maxIdx,i);
i = maxIdx;
L = 2*i + 1;
}
}
private void swap(int[] arr,int i,int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
三: 上面的方法是自己重新建了一个堆(开了O(k)的额外的空间),其实也可以直接在input数组中建堆(修改了原数组),并且建堆的时候,直接从第一个非叶子结点开始建,也就是heapfiy的加速过程,这样就不需要siftUp的过程,一开始就是从第一个非叶子( (k-1)-1) / 2结点直接siftDown: 而且这里我把siftDown写成递归的形式。
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer>res = new ArrayList<>();
if(input == null || k <= 0 || k > input.length)
return res;
for(int i = (k-1-1)/2; i >= 0; i--)//一个k个数的堆,从第一个非叶子结点开始调整 (k-1-1)/2 本来是(k-1)/2,但是下标是k-1
siftDown(input,i,k);
for(int i = k; i < input.length; i++){
if(input[i] < input[0]){
swap(input,i,0);
siftDown(input,0,k);
}
}
for(int i = 0; i < k; i++)
res.add(input[i]);
return res;
}
//递归调整 下沉 //这是最大堆
private void siftDown(int[] arr,int i,int heapSize){
int L = 2 * i + 1;
int R = 2 * i + 2;
int maxIdx = i;
if(L < heapSize && arr[L] > arr[maxIdx]) maxIdx = L;
if(R < heapSize && arr[R] > arr[maxIdx]) maxIdx = R;
if(maxIdx != i){
swap(arr,i,maxIdx);
siftDown(arr,maxIdx,heapSize);//继续调整孩子
}
}
private void swap(int[] arr,int i,int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
连续子数组的最大和
题目链接
题目
解析
这个题目和LeetCode-53. Maximum Subarray一模一样,可以看那篇博客,那篇博客还写了一个求端点的扩展,不重复解释。
递归:
public class Solution {
private int res;
public int FindGreatestSumOfSubArray(int[] array) {
if(array == null || array.length == 0)
return 0;
res = array[0];
process(array,array.length-1);
return res;
}
private int process(int[] arr,int i){
if(i == 0)
return arr[0];
else {
int pre = process(arr,i-1); //你先给我求出前面的最大子序和
int cur = pre > 0 ? pre + arr[i] : arr[i];
res = Math.max(res,cur);
return cur;
}
}
}
一维dp:
public class Solution {
//最大连续子序列的和
public int FindGreatestSumOfSubArray(int[] array) {
if(array == null || array.length == 0)
return 0;
int[] ends = new int[array.length];
ends[0] = array[0];
int res = array[0];
for(int i = 1; i < array.length; i++){
ends[i] = ends[i-1] > 0 ? ends[i-1] + array[i] : array[i];
res = Math.max(res,ends[i]);
}
return res;
}
}
滚动优化:
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
if(array == null || array.length == 0)
return 0;
int res = array[0];
int preMax = array[0];
for(int i = 1; i < array.length; i++){
preMax = preMax > 0 ? array[i] + preMax : array[i];
res = Math.max(res,preMax);
}
return res;
}
}
分治,这里要注意边界LMax = process(arr,L,mid);不是mid - 1,因为边界是L == R返回的arr[L]。
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
if(array == null || array.length == 0)
return 0;
return process(array,0,array.length-1);
}
//返回这个之间的最大子序和
private int process(int[] arr,int L,int R){
if(L == R)
return arr[L];
int mid = L + (R - L)/2;
int LMax = process(arr,L,mid);
int RMax = process(arr,mid+1,R);
int sum = 0,LSumMax = Integer.MIN_VALUE,RSumMax = Integer.MIN_VALUE;
for(int i = mid; i >= L; i--){
sum += arr[i];
if(sum > LSumMax){
LSumMax = sum;
}
}
sum = 0;
for(int i = mid + 1; i <= R; i++){
sum += arr[i];
if(sum > RSumMax){
RSumMax = sum;
}
}
int crossMax = LSumMax + RSumMax;
//compare crossMax、LMax,RMax
if(LMax >= RMax && LMax >= crossMax)
return LMax;
if(RMax >= LMax && RMax >= crossMax)
return RMax;
return crossMax;
}
}