剑指offer思路 (40-49)

40. 数组中只出现一次的数字

题:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

思路:1)HashMap

2)异或:两个相同数字异或=0,一个数和0异或还是它本身。
依次异或,剩下的肯定是那两个只出现一次的数的异或结果。
这个结果的二进制中的1,表现的是A和B的不同的位。 我们就取第一个1所在的位数,假设是第3位,接着把原数组分成两组,分组标准是第3位是否为1。如此,相同的数肯定在一个组,因为相同数字所有位都相同,而不同的数,肯定不在一组。然后把这两个组按照最开始的思路,依次异或,剩余的两个结果就是这两个只出现一次的数字。
注意 :在异或的时候,分组方法很重要。即:为1 的位代表异或的两个数在该位取了不同的值,所以以该位为标准,将该位为1的存入num1[0],反之存入num2[0],然后每次就在两个数组内部异或,最后留下的就是所求。

代码:
1) hashMap:

 public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int count = 0;

        int length = array.length;

        Map<Integer,Integer> map = new HashMap<>();

        for (int i = 0;i < length; i++) {
            if (!map.containsKey(array[i])) {
                map.put(array[i],1);
            }
            else {
                map.remove(array[i]);
                map.put(array[i],2);
            }
        }

        Iterator it = map.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            if (1 == (int)entry.getValue()) {
                if (count == 0) {
                    num1[0] = (int) entry.getKey();
                    count++;
                }
                else {
                    num2[0] = (int) entry.getKey();
                    break;
                }
            }
        }
    }

2)※异或

public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2)    {
        int length = array.length;
        if(length == 2){
            num1[0] = array[0];
            num2[0] = array[1];
            return;
        }
        int bitResult = 0;
        for(int i = 0; i < length; ++i){
            bitResult ^= array[i];
        }
        int index = findFirst1(bitResult);
        for(int i = 0; i < length; ++i){
            if(isBit1(array[i], index)){
                num1[0] ^= array[i];
            }else{
                num2[0] ^= array[i];
            }
        }
    }

    private int findFirst1(int bitResult){
        int index = 0;
        while(((bitResult & 1) == 0) && index < 32){
            bitResult >>= 1;
            index++;
        }
        return index;
    }

    private boolean isBit1(int target, int index){
        return ((target >> index) & 1) == 1;
    }

           


42. 和为S的正整数序列

题:输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序。


思路:
1) 重点是回溯
2) 滑动时间窗口


代码:
1) 回溯

public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer>> resList = new ArrayList<>();
        int nowSum = 0;
        int start = 1;
        for (int i = 1 ;i <= sum ; i++) {
            nowSum += i;
            if (nowSum > sum) {
                i = start ;
                start++;
                nowSum = 0;
            }
            else if (nowSum == sum) {
                if (start == i) {
                    break;
                }
                ArrayList<Integer> list = new ArrayList<>();
                for (int j = start; j <= i; j++) {
                    list.add(j);
                }
                resList.add(list);
                start = start + 1;
                i = start;
                nowSum = start;
            }
            else {
                continue;
            }
        }

        return resList;
    }

2) 滑动时间窗口

public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
        //存放结果
        ArrayList<ArrayList<Integer> > result = new ArrayList<>();
        //两个起点,相当于动态窗口的两边,根据其窗口内的值的和来确定窗口的位置和大小
        int plow = 1,phigh = 2;
        while (phigh > plow) {
//            等差数列求和Sn = n(a1+an)/2
            int nowSum = (plow+phigh) * (phigh-plow+1) / 2;
            if (nowSum == sum) {
                ArrayList<Integer> list = new ArrayList<>();
                for (int i = plow; i <= phigh; i++) {
                    list.add(i);
                }
                result.add(list);
                plow++;
            }
            else if (nowSum < sum) {
                phigh++;
            }
            else {
                plow++;
            }
        }

        return result;
    }



42. 和为S的两个数字

题:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。


思路:
1) 双指针,类比快排
一个指针从前往后,一个指针从后往前。大的话后面的往前移,小的话前面的往后移。相等的时候要比较乘积。不能跳出循环,一定要遍历完所有的数字
重点是 比较完相等记得探针要改变,否则无限循环
Arraylist是怎么存怎么取的,所以因为要求小的先输出,本来就是从小到大排序,所以就按照顺序存入即可。


2) HashMap
map.put(array[i], 1),map.put(sum - array[i],1)
重点是:当有多个相同的数字的时候的情况,要考虑完全!


代码: 1) 双指针
public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
        ArrayList<Integer> list = new ArrayList<>();

        int i = 0, j = array.length - 1;

        while (i < j) {
            while (array[i] + array[j] > sum && i < j) {
                j--;
            }
            while (array[i] + array[j] < sum && i < j) {
                i++;
            }
            if (array[i] + array[j] == sum) {
                if (list.isEmpty()) {
                    list.add(array[i]);
                    list.add(array[j]);
                } else {
                    int multi1 = list.get(0) * list.get(1);
                    int multi2 = array[i] * array[j];
                    if (multi2 < multi1) {
                        list.clear();
                        list.add(array[i]);
                        list.add(array[j]);
                    }
                }
                i++;
                j--;
            }
        }


        return list;
    }

2) HashMap:
public ArrayList<Integer> FindNumbersWithSum(int [] array, int sum) {
        ArrayList<Integer> list = new ArrayList<>();

        if (array == null || array.length <= 0) {
            return list;
        }

        Map<Integer,Integer> map = new HashMap<>();

        int count = 0;

        for (int i = 0; i < array.length; i++) {
            if (map.containsKey(array[i])) {
                if (count == 0) {
                    list.add(sum-array[i]);
                    list.add(array[i]);
                    count++;
                }
                int mul = array[i] * (sum - array[i]);
                if (mul < (list.get(0)) * list.get(1)) {
                    list.clear();

                    list.add(sum-array[i]);
                    list.add(array[i]);
                }

            }else {
                map.put(array[i],1);
                if (sum - array[i] != array[i]) {
                    map.put(sum-array[i],1);
                }
                else {
                    int tmp = map.get(array[i]) + 1;
                    map.remove(array[i]);
                    map.put(array[i],tmp);
                }
            }
        }

//        Collections.sort(list);
        return list;
    }



43. 左旋转字符串

题:对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。

思路:
1)StringBuilder拼接
2)子串
3)三次旋转(YX = (XTYT)T
注:swap的时候,把String转成字符串数组,是为了更改的时候比较好更改


代码:
1)StringBuilder:

public String LeftRotateString(String str,int n) {
        if (n > str.length()) {
            return "";
        }
        StringBuilder sb1 = new StringBuilder();
        StringBuilder sb2 = new StringBuilder();

        for (int i = 0; i < n; i++) {
            sb1.append(str.charAt(i));
        }
        for (int i = n; i < str.length(); i ++) {
            sb2.append(str.charAt(i));
        }

        StringBuilder newStr = sb2.append(sb1);

        return newStr.toString();
    }

2)子串
public String LeftRotateString(String str,int n) {
        int length = str.length();
        if (length <= 0) return "";

        n = n % length;
        str += str;

        return str.substring(n,n+length);
    }

3)旋转
public String LeftRotateString(String str,int n) {
        int len = str.length();
        if (len <= 0) return "";

        n = n % len;

        char[] array = str.toCharArray();

        for (int i = 0,j = n-1;j > i; i++,j--) {
            swap(array,i,j);
        }
        for (int i = n,j =len-1;j > i; i++,j--) {
            swap(array,i,j);
        }
        for (int i = 0,j = len-1;j > i; i++,j--) {
            swap(array,i,j);
        }

        return new String(array);
    }



44. 翻转单词序列

题:例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

思路:先翻转整个句子,再翻转每个单词

代码:

public String ReverseSentence(String str) {
        if (str == null || str.length() <= 0) {
            return "";
        }

        int len = str.length();
        char[] chars = str.toCharArray();

        /**先反转整个句子*/
        for (int i = 0,j = len - 1; j > i; i++, j--) {
            swap(chars,i,j);
        }

        /**再反转每个单词*/
        int i = 0;
        int s = 0, e = 0;
        while (i < len) {
            // 找到第一个字符开始的位置
            while (i < len && chars[i] == ' '){
                i++;
            }
            s = e = i;
            // 找到这个单词最后一个字符的位置,跳出的时候e是空格
            while (i < len && chars[i] != ' ') {
                i++;
                e++;
            }
            reverseWords(chars,s,e-1);
        }

        return new String(chars);
    }

    private void swap(char[] chars,int i,int j) {
        char tmp = chars[i];
        chars[i] = chars[j];
        chars[j] = tmp;

    }

    private void reverseWords(char[] chars,int start,int end) {
        for (int i = start,j = end; j > i; i++,j--) {
            swap(chars,i,j);
        }
    }



45. 扑克牌顺子

题:大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

思路:
1) 不能有重复(除了0)
2) 差的绝对值要 < 5

代码:

public boolean isContinuous(int [] numbers) {
        if (numbers == null || numbers.length <= 0) return false;
        Map<Integer,Integer> map = new HashMap<>();
        int min = 99;
        int max = -1;
        int count0 = 0;
        for (int i = 0 ; i < numbers.length; i++) {
            if (!map.containsKey(numbers[i])) {
                map.put(numbers[i],1);
                if (numbers[i] < min && numbers[i] != 0) {
                    min = numbers[i];
                }
                if (numbers[i] > max && numbers[i] != 0) {
                    max = numbers[i];
                }
                if (max - min >= 5) {
                    return false;
                }
            }
            else {
//                除了0不能重复
                if (numbers[i] != 0) {
                    return false;
                }
                else {
                    continue;
                }
            }
        }

        return true;
    }



※46. 孩子们的游戏

题:随机指定一个数m,让编号为0的开始报数。每次喊到m-1的那个不再回到圈中,从他的下一个开始,继续0…m-1报数…直到剩下最后一个小朋友,可以不用表演,请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)。如果没有小朋友,请返回-1

思路:直接用模拟游戏过程

注:使用LinkedList优于ArrayList,因为只涉及插入和删除,不涉及查询,链表效率更高

代码:

public int LastRemaining_Solution(int n, int m) {
        if(n == 0 || m == 0) return -1;

        List<Integer> list = new LinkedList<>();
        for (int i = 0;i < n; i++) {
            list.add(i);
        }
        int bt = 0;
        while (list.size() > 1) {
            bt = (bt + m - 1) % list.size();
            list.remove(bt);
        }

        return list.size() == 1 ? list.get(0) : -1;
    }



47. 1+2+3+…+n

题:求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

思路:短路求值!!,利用逻辑运算的性质,比如&&运算当前一部分为false的时候后面就不会执行,模拟递归结束。

代码:

public int Sum_Solution(int n) {
        int sum = n;
        boolean ans = (sum > 0) && (sum += Sum_Solution(n-1))>0;
        return sum;
    }



48. 不用加减乘除做加法

题:写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

思路:用二进制与运算代替
加法:异或操作^;进位:与操作&再左移一位。
按照十进制的思路来,例如5+7
二进制就是101+111,先算加法(不带进位)即101^111=010;再算进位(纯进位)(101&111)<<1 = 1010;
再重复上面的(此时num1=加法结果010,num2=进位结果1010)直到进位为0

代码:

public int Add(int num1,int num2) {
        while (num2 != 0) {
            int sum1 = num1 ^ num2;
            num2 = (num1 & num2) << 1;
            num1 = sum1;
        }
        return num1;
    }



49. 把字符串转换成整数

题:将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。输入一个字符串,包括数字字母符号,可以为空。如果是合法的数值表达则返回该数字,否则返回0

思路:
有几个注意点:
1)前面有没有符号位
2)溢出的处理(尤其是 负数是-2147483648,正数是+2147483647),代码里面的重点

代码:

public int StrToInt(String str) {
        if (str == null || str.length() <= 0) {
            return 0;
        }

        char[] array = str.toCharArray();
        int flag = 1;
        if (array[0] == '+') {
            flag = 1;
        }
        if (array[0] == '-') {
            flag = -1;
        }

        int res = 0;

        for (int i = ((array[0] == '+' || array[0] == '-') ? 1 : 0); i < array.length; i++) {
            if (!(array[i] >= '0' && array[i] <= '9')) {
                return 0;
            }

//            溢出处理
            if(flag == 1 && res * 10 > Integer.MAX_VALUE - (int)(array[i] - '0'))
                return 0;
            if(flag == -1 && res * (-10) < Integer.MIN_VALUE + (int)(array[i] - '0'))
                return 0;
            res = res * 10 + (array[i] - '0');

        }

        return res * flag;

    }
发布了53 篇原创文章 · 获赞 5 · 访问量 1519

猜你喜欢

转载自blog.csdn.net/zhicheshu4749/article/details/103697088