26.复杂链表的复制
/*
* 复杂链表的复制,每个节点除了next域还有一个sibling域指向任意节点
* 如果按照next顺序复制,那么sibling域可能指向一个目前还未复制的节点,并且需要再从头搜索一遍
* 本思路参考《剑指offer》,分3步
* 第一步,在原链表的每个原节点后复制一个相同节点,此时只指定了next域,即A指向A'
* 第二步,从头根据原节点的sibling指定刚复制节点的sibling,即若A的sibling指向E,则A'指向E',
* 即下一个,这就是第一步在原节点后复制节点的意义
* 第三步,拆分奇偶节点形成2个链表,即得到原复制链表的复制
*/
public ComplexListNode copyComplexLink(ComplexListNode pHead)
{
if(pHead==null) return null;
addCopiedNode(pHead);
addSibling(pHead);
ComplexListNode copiedNode = divideOddEvenNode(pHead);
return copiedNode;
}
//根据每个原节点,创建一个等值的节点,并插入到原节点与其下一个节点之间,即原节点的next域改为指向副本
public void addCopiedNode(ComplexListNode pHead)
{
if(pHead==null) return;
ComplexListNode node = pHead;
while(node!=null)
{
ComplexListNode copiedNode = new ComplexListNode(node.val);
ComplexListNode temp = node.next;
node.next = copiedNode;
copiedNode.next = temp;
node = temp;
}
}
//参照原节点,增加副本节点的sibling域,指向原节点的sibling域的下一个节点
public static void addSibling(ComplexListNode pHead)
{
ComplexListNode node = pHead;
while(node!=null)
{
if(node.sibling!=null) node.next.sibling = node.sibling.next;
node =node.next.next;
}
}
//拆分奇偶节点,组成2个链表,返回副本的头节点,从而将复制的复杂链表从原链表中分离出来
public ComplexListNode divideOddEvenNode(ComplexListNode pHead)
{
if(pHead==null) return null;
ComplexListNode node = pHead;
ComplexListNode copiedHead = pHead.next;
ComplexListNode copiedNode = copiedHead;
while(node!=null)
{
copiedNode = node.next;
ComplexListNode temp = copiedNode.next;
node.next = temp;
node = node.next;
if(node!=null) copiedNode.next =node.next;
else copiedNode.next = null;
}
return copiedHead;
}
27.二叉搜索树与双向链表
/* 将二叉搜索树转换为双向链表
* 思路:使用循环中序遍历,记录一个上一节点
*/
public TreeNode binarySearchTree2Delink(TreeNode root)
{
if(root==null) return null;
TreeNode node = root;
TreeNode lastNode =null;
Stack<TreeNode> stack = new Stack<TreeNode>();
while(node!=null||!stack.isEmpty())
{
while(node!=null)
{
stack.push(node);
node = node.left;
}
//if(!stack.isEmpty())
//{
node = stack.pop();
node.left =lastNode;
if(lastNode!=null) lastNode.right =node;
lastNode =node;
node =node.right;
//}
}
TreeNode pHead= root;
while(pHead!=null&&pHead.left!=null) pHead =pHead.left;
return pHead;
}
29.数组中出现次数超过一半的数字
/*
* 数组中出现次数超过一半的数字
* 以某个数为起始标记,遍历数组,相同+1,不同-1,次数为0则将当前数字作为标记,次数置为1
* 如下是剑指offer的实现,缺陷,对于没有出现次数超过一半的数字的数组,则返回的是最后一次的标记值
* 可以在选出数据后再遍历一次,校验
*/
public int moreThanHalfNum(int[] a)
{
boolean isValid = false;
if(a==null ||a.length==0) return Integer.MIN_VALUE;
int result = a[0];
int times = 1;
for(int i=1;i<a.length;i++)
{
if(times==0)
{
result = a[i];
times = 1;
}
if(a[i]==result) times++;
else times--;
}
//传递的数组可能根本没有出现次数超过一半的数字,所以再遍历一次
int num = 0;
for(int j=0;j<a.length;j++)
{
if(a[j]==result) num++;
}
if(num>(a.length/2)) isValid = true;
return result;
}
31.连续子数组的最大和
public int maxSubArray(int[] array)
{
//若考虑本身的和可能为0,则可以设个标记变量来区分这个0是所求得的还是由于空数组的
if(array==null||array.length==0) return 0;
int maxSum=0x80000000;//int的最小值
int curSum=0;
for(int i=0;i<array.length;i++)
{
if(curSum<=0) curSum=array[i];
else curSum+=array[i];
if(curSum>maxSum) maxSum = curSum;
}
return maxSum;
}
34.丑数(只能被2、3、5整除的数)
// 寻找第n个丑数
/*
* 思路:第1个数设为1,将已找到的丑数存起来,那么下一个丑数一定来自于前面某个丑数*2,或者*3,或者*5,
* 已有的丑数也是这么计算来的,那么需要分别记录上一次乘2、乘3、乘5之前的位置,下一次的丑数一定来自这些位置
* 的数计算后的结果,每次取这3个位置的值计算后的最小值
* 启发:和自底向上计算斐波那契数列的思路十分相似,只是斐波那契数列的计算只需要保存2个索引,而这里保存3个
*/
public int uglyNumber(int num) {
if (num <= 0)
return 0;
int[] result = new int[num];
int index = 1;
int index_2 = 0;
int index_3 = 0;
int index_5 = 0;
result[0] = 1;
while (index < num) {
int min = min(result[index_2] * 2, result[index_3] * 3, result[index_5] * 5);
result[index++] = min;
if (result[index_2] * 2 == min)
index_2++;
if (result[index_3] * 3 == min)
index_3++;
if (result[index_5] * 5 == min)
index_5++;
}
return result[num - 1];
}
37.两个链表的第一个公共节点
/*
* 寻找2个链表的第一个公共节点
* 思路:使用2个辅助栈分别存储2个链表的节点,然后出栈,最后一个相同的节点即为第一个公共节点
*/
public Object firstCommonNode(List l1,List l2)
{
if(l1.isEmpty()||l2.isEmpty()) return null;
Object result=null;
Stack s1 = new Stack();
Stack s2 = new Stack();
for(int i=0;i<l1.size();i++)
{
s1.push(l1.get(i));
}
for(int j=0;j<l2.size();j++)
{
s2.push(l2.get(j));
}
while(!s1.isEmpty()&&!s2.isEmpty())
{
Object obj1 = s1.pop();
Object obj2 = s2.pop();
//s1.lastElement()
if(obj1==obj2)
{
result = obj1;
}
else break;
}
return result;
}
/*参考牛客的实现,其实质是找到2个链表的长度差异,即起点不一样,然后一起往后移,若有公共节点,一定是一起到达
*只不过寻找长度差异的方式比较巧妙且隐晦。2个指针一起遍历,发现为空则指向另一个链表,那么2个链表都遍历完时,其
*指向的链表其实已经交换了,且先走完的那一个此时已经在长链表中走了n步(n为2个链表的长度差),这就是寻找长度差
*的过程,若没有公共节点,则将一起走到各自链表的末尾,即都为null
**/
public ListNode FindFirstCommonNode( ListNode pHead1, ListNode pHead2) {
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while(p1!=p2){
p1 = (p1==null ? pHead2 : p1.next);
p2 = (p2==null ? pHead1 : p2.next);
}
return p1;
}
38.数字在排序数组中出现的次数
/*
* 数字在排序数组中出现的次数
* 采用二分查找先找到数字K,再向前向后搜索
* 极端情况下数组中的数字全是K,此时的效率等于从头到尾查找
* 牛客效率:22ms,9380k
*/
public int getNumberOfKInSortedArray(int[] array, int k)
{
if(array==null||array.length==0) return 0;
int low=0;
int high =array.length-1;
int mid =0;
while(low<=high)
{
mid =(low+high)/2;
if(array[mid]==k) break;
else if(array[mid]<k) low =mid+1;
else high =mid-1;
}
//如果不等,表示没找到目标数字
if(array[mid]!=k) return 0;
int count =0;
int index =mid;
while(index>=0&&array[index]==k)//index的判断必须放前面,否则可能数组越界
{
count++;
index--;
}
index =mid+1;
while(index<array.length&&array[index]==k)
{
count++;
index++;
}
return count;
}
/*
* 数字在排序数组中出现的次数,通过二分法分别查找目标数字的起始索引和终止索引
* 牛客效率:14ms,9356k
*/
public int getNumberOfKInSortedArray1(int[] array, int k)
{
if(array==null||array.length==0) return 0;
int number =0;
int start =getFirstK(array, k, 0, array.length-1);
int last = getLastK(array, k, 0, array.length-1);
if(start!=-1&&last!=-1) number =last-start+1;
return number;
}
public int getFirstK(int[] array, int k , int start, int end)
{
if(array==null||array.length==0||end<start) return -1;
int mid =-1;
while(start<=end)
{
mid =(start+end)/2;
if(array[mid]==k)
{
if(mid>=1&&array[mid-1]==k) end =mid-1;
else break;
}
else if(array[mid]<k) start =mid+1;
else end =mid-1;
}
if(array[mid]!=k) mid =-1;
System.out.println("起始索引:"+mid);
return mid;
}
public static int getLastK(int[] array, int k , int start, int end)
{
if(array==null||array.length==0||end<start) return -1;
int mid =-1;
while(start<=end)
{
mid =(start+end)/2;
if(array[mid]==k)
{
if(mid<array.length-1&&array[mid+1]==k) start =mid+1;
else break;
}
else if(array[mid]>k) end =mid-1;
else start =mid+1;
}
if(array[mid]!=k) mid =-1;
System.out.println("终止索引:"+mid);
return mid;
}
40.数组中只出现一次的数字
/*
* 数组中有2个只出现一次的数字
* 第一步:将所有的数进行异或,正常来说会得到一个非0的数,这个非0的数的二进制中至少有一个1,这个1只来自那
* 两个数其中的一个
* 第二步:求出只有这个位置是1的数,命名为flag。原数组中的数在此位置要么为1,要么不为1,只出现一次的2个数
* 必定只属于其中一种
* 第三步:将flag与原数组的数字进行与,若等于flag则将result1与数组元素异或,若不等则将result2与
* 数组元素异或
*/
public void findNumsAppearOnce(int[] array, int num1[], int num2[])
{
int result =0;
for(int i=0;i<array.length;i++) result ^=array[i];
int flag_1 =findBinaryOne(result);
for(int i=0;i<array.length;i++)
{
if((flag_1&array[i])==flag_1) num1[0] ^=array[i];
else num2[0] ^=array[i];
}
}
public int findBinaryOne(int num)
{
if(num==0) return 0;
int num1=1;
while(num1!=(num1&num)) num1 =num1<<1;
System.out.println(num1);
return num1;
}
41.和为s的两个数字VS和为s的连续正数序列
/*
* 从排序数组中查找和为某值的一对整数
* 从2头向中间搜索,时间复杂度O(n)
*/
public void findNumbersWithFixedSum(int[] array ,int k)
{
if(array==null||array.length==0) return;
int start =0;
int end =array.length-1;
while(start<=end&&start>=0&&end<=array.length-1)
{
if(array[start]+array[end]==k)
{
System.out.println(array[start]+":"+array[end]);
break;
}
else if(array[start]+array[end]<k) start++;
else end--;
}
}
/*
* 从数组中找出和为某值的一个连续序列
* 从起始点往另一端搜索,用2个索引,大了则起始索引后移,小了则结束索引后移
*/
public void findSequenceWithFixedSum(int[] array, int k)
{
if(array==null||array.length<2) return;
int start =0;
int end =1;
while(array[start]<=(1+k)/2&&end<array.length)//start<end&&
{
int temp =0;
for(int i=start;i<=end;i++) temp+=array[i];
if(temp==k&&start<end)
{
for(int j=start;j<=end;j++) System.out.println(array[j]);
end++;
}
else if(temp<k) end++;
else start++;
}
}
43.n个骰子的点数
/*
* 计算n个骰子的各种值出现的概率
* 第一步统计各值出现的频次
* 第二步根据频次计算概率
* 计算频次时,n个骰子的各种值的计算可以参考n-1个的场景,比如3个骰子和为5,则一定来源于2个骰子的和为
* 2、3、4的场景的和,此时第3个骰子分别出3、2、1,表达出来即f(n,k),n为骰子数,k为n个骰子的和,则
* 当k<=6,f(n,k)=f(n-1,n-1)+f(n-1,n)+...+f(n-1,k-1)
* 而3个骰子和为9则一定来源于2个骰子和为8、7、6、5、4、3,第三颗骰子值为1、2、3、4、5、6,即当k>6,
* f(n,k)=f(n-1,k-6)+f(n-1,k-5)+...+f(n-1,k-1)
*/
public void ratioOfKDices(int n)
{
if(n<1) return;
int Max =6;
/* 定义一个2行的二维数组,1个记录f(n-1,sum),一个记录f(n,sum),
* 即一个记录上一轮的值,一个存储当前轮的值
*/
int[][] sum =new int[2][Max*n+1];
//flag作为标记,2个数组的存储交替使用,因为下一轮需用到上一轮的计算结果
int flag =0;
for(int i=1;i<=Max;i++) sum[0][i] =1;
for(int j=2;j<=n;j++)
{
//n个骰子的和不会低于n,因而小于n的均重置为0
for(int x=0;x<=j;x++) sum[1-flag][x] =0;
for(int k=j;k<=Max*j;k++)
{
//由于作为当前计算值的素组记录了前面的值,这里应当清0在累加
sum[1-flag][k] =0;
if(k<Max)
{
for(int y=j-1;y<k;y++) sum[1-flag][k] +=sum[flag][y];
}
else
{
for(int y=k-Max;y<=k-1;y++) sum[1-flag][k] +=sum[flag][y];
}
}
flag =1-flag;
}
for(int i=n;i<=Max*n;i++)
{
System.out.println("f("+n+","+i+")="+sum[flag][i]);
}
ratio(sum[flag],n,Max);
//ratio1(sum[flag],n,Max,m);
}
public void ratio(int[] sum, int n, int max)
{
double total =Math.pow(max,n);
double ratio =0;
double ratio_sum =0;
for(int i=n;i<sum.length;i++)
{
ratio =sum[i]/total;
System.out.println("r("+n+","+i+")="+ratio);
ratio_sum +=ratio;
}
System.out.println(ratio_sum);
}
//将n个骰子d和超过k的概率用最简分数的形式输出
public void ratio1(int[] sum, int n, int max, int k)
{
if(k<n||k>n*max) return;
int result =0;
for(int i=k;i<=n*max;i++)
{
result +=sum[i];
}
change(result,(int)Math.pow(max, n));
}
//求最简分数形式,分母是6的幂
public void change(int son, int mother)
{
//因为分母是6的次方,因而公因子只可能为2或者3,如果扩展成其它数,可以先求出其因子
while(son%2==0&&mother%2==0)
{
son /=2;
mother /=2;
}
while(son%3==0&&mother%3==0)
{
son /=3;
mother /=3;
}
System.out.println(son+"/"+mother);
}
44.扑克牌的顺子
/*
* 扑克牌中的顺子
* 大小王作为赖子,可以顶替任何牌
* 思路:先排序,求赖子的数量,值越界则不是,除赖子以外有相同牌不是,赖子数量少于缺口也不是
*/
public boolean isContinuous(int[] array)
{
int length =5;
if(array==null||array.length!=length) return false;
int left =0;//左边界
int right =13;//右边界
int special =0;//特例
quickSort(array,0,array.length-1);
boolean flag =false;
int count_0 =0;
for(int i=0;i<array.length;i++)
{
if(array[i]<left||array[i]>right) return flag;
else if(array[i]==special) count_0++;
else if(i<array.length-1&&array[i]==array[i+1]) return flag;
}
flag =true;
for(int j=1;j<array.length;j++)
{
if(array[j]!=0&&array[j-1]!=0&&array[j]-array[j-1]>1) count_0 -=(array[j]-array[j-1]-1);
if(count_0<0) return false;
}
return flag;
}
public void quickSort(int[] array, int low, int high)
{
if(array==null||array.length==0||low>=high) return;
int left =low;
int right =high;
while(left<right)
{
int temp =array[left];
while(temp<=array[right]&&left<right) right--;
if(left<right)
{
temp =array[right];
array[right] =array[left];
array[left] =temp;
}
while(left<right&&array[left]<=array[right]) left++;
if(left<right)
{
temp =array[right];
array[right] =array[left];
array[left] =temp;
}
}
quickSort(array,low,left-1);
quickSort(array,right+1,high);
}
47.不用加减乘除做加法
/* 不用加减乘除计算加法,则只能用位操作
* 若没有进位,则m+n=m^n,若m&n!=0则表示有进位,若只有一个进位,则m+n={(m^n)^[(m&n)<<1]}<<1
* 若与的结果不为0,则需要继续进行计算
*/
public int addWithOperator(int m ,int n)
{
int result1 =m^n;
int result2 =(m&n)<<1;
int result =0;
while(result1!=0)
{
int temp =(result1&result2)<<1;
result2 =result1^result2;
result1 =temp;
result =result1^result2;
}
return result;
}
public int addWithOperator1(int m ,int n)
{
int result1 =m;
int result2 =n;
while(result2!=0)
{
int temp =result1^result2;
result2 =(result1&result2)<<1;
result1 =temp;
}
return result1;
}