剑指offer (java)(27-37)

27.输入一个字符串,按字典序打印出该字符串中字符的所有排列。

例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

//使用全排列+容器排序的方法
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
public class Solution {
    public ArrayList<String> Permutation(String str) {
       List<String> list = new ArrayList<>();
        char[] ch = str.toCharArray();
        if(str!=null&&str.length()!=0){
            PermutationHelper(ch,0,list);
            Collections.sort(list);
        }
        return (ArrayList)list;
    }
//全排列:递归+回溯法
    public void PermutationHelper(char[] ch,int i,List<String> list){
        if(i==ch.length-1){
            String var = String.valueOf(ch);
            if(!list.contains(var)){
                list.add(var);
            } 
            return;
        }else{
            for(int j=i;j<ch.length;j++){
                swap(ch,i,j);
                PermutationHelper(ch,i+1,list);
                swap(ch,i,j);
            }
        }
    }
    void swap(char[]ch,int i,int j){
        char temp;
        temp = ch[i];
        ch[i] = ch[j];
        ch[j] = temp;
    }
}

28.数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

思路一:数组排序后,如果符合条件的数存在,则一定是数组中间那个数。(比如:1,2,2,2,3;或2,2,2,3,4;或2,3,4,4,4等等)
这种方法虽然容易理解,但由于涉及到快排sort,其时间复杂度为O(NlogN)并非最优;

思路二:如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多。
在遍历数组时保存两个值:一是数组中一个数字,一是次数。遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。遍历结束后,所保存的数字即为所求。然后再判断它是否符合条件即可。
参考代码如下:

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array==null||array.length==0){
            return 0;
        }
        int count1 = 0;
        int val = 0;
        for(int i=0;i<array.length;i++){
            if(count1==0){
                val = array[i];
            }
            if(val == array[i]){
                count1++;
            }else{
                count1--;
            }
        }
        if(count1>0){
            int count2 = 0;
            for(int i=0;i<array.length;i++){
                if(array[i]==val){
                    count2++;
                }
            }
            return array.length/count2<2 ? val : 0;
        }else{
            return 0;
        }
    }
}

29.输入n个整数,找出其中最小的K个数。

例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

import java.util.*;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        if(k>input.length){
            return list;
        }
     
        for(int i=0;i<input.length;i++){
            for(int j=input.length-1;j>i;j--){
                if(input[j]<input[j-1]){
                    swap(input,j-1,j);
                }
            }
        }
        for(int i=0;i<k;i++){
            list.add(input[i]);
        }
        return list;
    }
    private void swap(int[] arr,int i,int j){
        int temp = arr[j];
        arr[j] = arr[i];
        arr[i] = temp;
    }
}

30.在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?

例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)


/*

使用动态规划
F(i):以array[i]为末尾元素的子数组的和的最大值,子数组的元素的相对位置不变
F(i)=max(F(i-1)+array[i] , array[i])
res:所有子数组的和的最大值
res=max(res,F(i))

如数组[6, -3, -2, 7, -15, 1, 2, 2]
初始状态:
    F(0)=6
    res=6
i=1:
    F(1)=max(F(0)-3,-3)=max(6-3,3)=3
    res=max(F(1),res)=max(3,6)=6
i=2:
    F(2)=max(F(1)-2,-2)=max(3-2,-2)=1
    res=max(F(2),res)=max(1,6)=6
i=3:
    F(3)=max(F(2)+7,7)=max(1+7,7)=8
    res=max(F(2),res)=max(8,6)=8
i=4:
    F(4)=max(F(3)-15,-15)=max(8-15,-15)=-7
    res=max(F(4),res)=max(-7,8)=8
以此类推
最终res的值为8
*/

public class Test30 {


    public  int FindGreatestSumOfSubArray(int[] array) {
        int f = array[0];
        int res = array[0];
        for(int i=1;i<array.length;i++){
            f = Math.max(f+array[i],array[i]);
            res=Math.max(res,f);
        }
        return res;
    }

    public static void main(String[] args) {
        int[] a ={6, -3, -2, 7, -15, 1, 2, 2,100} ;
        Test30 t = new Test30();
        int i = t.FindGreatestSumOfSubArray(a);
        System.out.println(i);
    }
}

31.求出113的整数中1出现的次数,并算出1001300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。

ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

//从1开始到指定的值挨个查看1的个数
// 每个数除以10取余,如果是1,个数+1
public class Test31 {
    public static void main(String[] args) {

    }
    public int NumberOf1Between1AndN_Solution(int n){
        int ans = 0;
        for (int i=1;i<=n;i++){
            int j=i;
            while (j!=0){
                if(j%10==1){
                    ans++;
                }
                j /= 10;
            }
        }
        return ans;
    }

}

32.输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

//本质还是排序,比较两个字符串s1, s2大小的时候,先将它们拼接起来
// 比较s1+s2,和s2+s1那个大,如果s1+s2大,那说明s2应该放前面
// 所以按这个规则,s2就应该排在s1前面。
//使用冒泡排序
public class Test32 {
    public String PrintMinNumber(int [] numbers) {

        for (int i=0;i<numbers.length-1;i++) {
            for (int j = numbers.length-1; j > i; j--) {
                String str1 = numbers[j-1] + ""+numbers[j];
                String str2 = numbers[j] + ""+numbers[j-1];
                //可能溢出,使用long
                if (Long.valueOf(str1) > Long.valueOf(str2)) {
                    swap(numbers,j-1,j);
                }
            }
        }
        String str = new String("");
        for(int i=0; i < numbers.length; i++)
            str = str + numbers[i];
        return str;

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

    public static void main(String[] args) {
        Test32 t=new Test32();
        int[] a = {3334,3,3333332};
        System.out.println(t.PrintMinNumber(a));
    }
}

33.把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。

习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

//通俗易懂的解释:
//首先从丑数的定义我们知道,
// 一个丑数的因子只有2,3,5,那么丑数p = 2 ^ x * 3 ^ y * 5 ^ z,
// 换句话说一个丑数一定由另一个丑数乘以2或者乘以3或者乘以5得到,
// 那么我们从1开始乘以2,3,5,就得到2,3,5三个丑数,
// 在从这三个丑数出发乘以2,3,5就得到4,6,10,6,9,15,10,15,25九个丑数,
// 我们发现这种方***得到重复的丑数,而且我们题目要求第N个丑数,
// 这样的方法得到的丑数也是无序的。那么我们可以维护三个队列:
//(1)丑数数组: 1
//乘以2的队列:2
//乘以3的队列:3
//乘以5的队列:5
//选择三个队列头最小的数2加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
//(2)丑数数组:1,2
//乘以2的队列:4
//乘以3的队列:3,6
//乘以5的队列:5,10
//选择三个队列头最小的数3加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
//(3)丑数数组:1,2,3
//乘以2的队列:4,6
//乘以3的队列:6,9
//乘以5的队列:5,10,15
//选择三个队列头里最小的数4加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
//(4)丑数数组:1,2,3,4
//乘以2的队列:6,8
//乘以3的队列:6,9,12
//乘以5的队列:5,10,15,20
//选择三个队列头里最小的数5加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
//(5)丑数数组:1,2,3,4,5
//乘以2的队列:6,8,10,
//乘以3的队列:6,9,12,15
//乘以5的队列:10,15,20,25
//选择三个队列头里最小的数6加入丑数数组,但我们发现,有两个队列头都为6,所以我们弹出两个队列头,同时将12,18,30放入三个队列;
//……………………
//疑问:
//1.为什么分三个队列?
//丑数数组里的数一定是有序的,因为我们是从丑数数组里的数乘以2,3,5选出的最小数,一定比以前未乘以2,3,5大,同时对于三个队列内部,按先后顺序乘以2,3,5分别放入,所以同一个队列内部也是有序的;
//2.为什么比较三个队列头部最小的数放入丑数数组?
//因为三个队列是有序的,所以取出三个头中最小的,等同于找到了三个队列所有数中最小的。
//实现思路:
//我们没有必要维护三个队列,只需要记录三个指针显示到达哪一步;“|”表示指针,arr表示丑数数组;
//(1)1
//|2
//|3
//|5
//目前指针指向0,0,0,队列头arr[0] * 2 = 2,  arr[0] * 3 = 3,  arr[0] * 5 = 5
//(2)1 2
//2 |4
//|3 6
//|5 10
//目前指针指向1,0,0,队列头arr[1] * 2 = 4,  arr[0] * 3 = 3, arr[0] * 5 = 5
//(3)1 2 3
//2| 4 6
//3 |6 9
//|5 10 15
//目前指针指向1,1,0,队列头arr[1] * 2 = 4,  arr[1] * 3 = 6, arr[0] * 5 = 5
public class Test33 {
    public int GetUglyNumber_Solution(int n) {
        //前6个丑数分别就是1-6
        if (n<7){
            return n;
        }
        int[] result = new int[n];
        result[0] = 1;
        int p2=0,p3=0,p5=0;
        for (int i=1;i<n;i++){
            result[i] = Math.min(result[p2]*2,Math.min(result[p3]*3,result[p5]*5));
            if(result[p2]*2==result[i]) p2++;
            if(result[p3]*3==result[i]) p3++;
            if(result[p5]*5==result[i]) p5++;
        }
        return result[n-1];

    }
}

34.在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

主要还是hash,利用每个字母的ASCII码作hash来作为数组的index。首先用一个58长度的数组来存储每个字母出现的次数,为什么是58呢,主要是由于A-Z对应的ASCII码为65-90,a-z对应的ASCII码值为97-122,而每个字母的index=int(word)-65,比如g=103-65=38,而数组中具体记录的内容是该字母出现的次数,最终遍历一遍字符串,找出第一个数组内容为1的字母就可以了,时间复杂度为O(n)


public class Test34 {
    public int FirstNotRepeatingChar(String str) {
        if (str==null){
            return -1;
        }
        int[] arr = new int[58];
        for(int i=0;i<str.length();i++){
            arr[(int)str.charAt(i)-65] += 1;
        }
        //需要再遍历字符串而不是数组,因为需要的字符串中的第一个
        for (int i=0;i<str.length();i++){
            if (arr[(int)str.charAt(i)-65]==1){
                return i;
            }
        }
        return -1;

    }

    public static void main(String[] args) {
        Test34 t = new Test34();
        System.out.println(t.FirstNotRepeatingChar("1"));
    }
}

35.

36.输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

/*
假定 List1长度: a+n  List2 长度:b+n, 且 a<b
那么 p1 会先到链表尾部, 这时p2 走到 a+n位置,将p1换成List2头部
接着p2 再走b+n-(n+a) =b-a 步到链表尾部,
这时p1也走到List2的b-a位置,还差a步就到可能的第一个公共节点。
将p2 换成 List1头部,p2走a步也到可能的第一个公共节点。
如果恰好p1==p2,那么p1就是第一个公共节点。  
或者p1和p2一起走n步到达列表尾部,二者没有公共节点,退出循环。 同理a>=b.
时间复杂度O(n+a+b)
         
       */
public class Test36 {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {

        ListNode p1 = pHead1, p2 = pHead2;
        while (p1!=p2){
            p1=(p1==null?pHead2 : p1.next);
            p2=(p2==null?pHead1 : p2.next);
        }
        return p1;
    }
}

37.统计一个数字在排序数组中出现的次数。

//思路一:二分查找,找到第一个位置和最后一个位置
//二分搜索的改进
public class Test37  {
    public int GetNumberOfK(int [] array , int k) {
        if(array==null||array.length==0){
            return 0;
        }
        int first = getFirst(array,k);
        int last = getLast(array,k);
        if (first==-1||last==-1)return 0;
        return last-first+1;

    }

    private int getFirst(int[] array, int k){
        int mid = 0;
        int low = 0, high = array.length-1;
        while (low <= high){
            mid = (low+high)/2;
            if (array[mid]==k){
                //在原二分搜索上进行改进
                if (mid==0||array[mid-1]!=k){
                    return mid;
                }else {
                    high = mid-1;
                }
            }else if(array[mid]>k){
                high = mid-1;
            }else {
                low = mid+1;
            }
        }
        return -1;
    }

    private int getLast(int[] array, int k){
        int mid = 0;
        int low = 0, high = array.length-1;
        while (low <= high){
            mid = (low+high)/2;
            if (array[mid]==k){
                //在原二分搜索上进行改进
                if (mid==array.length-1||array[mid+1]!=k){
                    return mid;
                }else {
                    low = mid+1;
                }
            }else if(array[mid]>k){
                high = mid-1;
            }else {
                low = mid+1;
            }
        }
        return -1;
    }
}

//思路二:因为data中都是整数,
// 所以可以稍微变一下,不是搜索k的两个位置,
// 而是搜索(k-0.5)和(k+0.5)这两个数应该插入的位置,然后相减即可。
//需要对二分法的代码进行简单的变形修改。
public class Test37_2 {
public int GetNumberOfK(int [] array , int k) {
if(array == null || array.length == 0)
return 0;
return biSearch(array, k + 0.5) - biSearch(array, k - 0.5);
}
private int biSearch(int[]array,double k){

    int low = 0, high = array.length-1, mid = 0;
    while (low<=high){
        mid = (low + high)/2;
        if (array[mid]>k){
            high = mid-1;
        }else {
            low = mid+1;
        }
    }
    return low;
}

}

posted @ 2020-02-10 20:12  程序员自习室  阅读(191)  评论(0)    收藏  举报