《剑指offer》Java版(二)

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;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值