剑指offer(十三)

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

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

思路:常规思路是HashMap统计次数,输出只出现一次的数。另一种比较厉害的思路是使用异或,我们知道相同的数异或之后的结果为0;不同的数,异或的结果就是两个数转换成二进制之后将相同位变为0之后的那个二进制数。因为异或运算满足交换律,因此题设中除了两个数只出现一次,其它数都出现两次,将所有数字异或,得到的值与这两个数直接异或的值是相同的。

假设上述异或后的值为1101,那么第一个1的位置为1,表示这两个数在第1位出现了不同。因此我们只需要将数组分为两组,第1位为1的一组,第1位不为1的一组,此时在这两组中,只有这两个数字是只出现一次的,在这两组中再进行异或操作,最后就能得到这两个数。

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.HashMap;
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        for(int n: array){
            if(!map.containsKey(n)) map.put(n, 1);
            else map.put(n, map.get(n) + 1);
        }
        int count = 1;
        for(int n: array){
            if(map.get(n) == 1 && count == 1) {
                num1[0] = n;
                count++;
            }
            else if(map.get(n) == 1 && count == 2) {
                num2[0] = n;
                break;
            }
        }
    }
}

解法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Solution {
    public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
        if (array == null || array.length < 2)
            return;
        int resultExclusiveNor = 0;
        for (int item : array)
            resultExclusiveNor ^= item;
        int firstIndex = findFirstIndex(resultExclusiveNor);
        num1[0]=0;
        num2[0]=0;
        for(int item:array){
            if(isBit1(item,firstIndex)) num1[0]^=item;
            else num2[0]^=item;
        }
    }

    public int findFirstIndex(int n) {
        int index = 0;
        while ((1 & n) == 0 && index < 32) {
            n = n >> 1;
            index++;
        }
        return index;
    }

    public boolean isBit1(int num, int index) {
        boolean check = false;
        num = num >> index;
        if ((num & 1) == 1) check = true;
        return check;
    }
}

41. 和为s的连续正数序列

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

输出描述:

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

思路: 设置两个游标,small表示解序列的头部元素,big表示解序列的尾部元素,若按求和公式,得到这个序列的和小于s,则令big加1;反之,small加1;若等于s,则存储该序列,big继续加1。另外还有很多数学上的解法,例如通过求和公式,利用small直接求解big。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        int small = 1, big = 2;
        ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
        while(small <= sum/2){
            int tmpSum = (small + big)*(big - small + 1)/2;
            if(tmpSum > sum) small++;
            else if (tmpSum < sum) big++;
            else{
                ArrayList<Integer> sequence = new ArrayList<Integer>();
                for(int i = small; i <= big; i++){
                    sequence.add(i);
                }
                res.add(sequence);
                big++;
            }
        }
        return res;
    }
}

42. 和为S的两个数

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

输出描述:

对应每个测试案例,输出两个数,小的先输出。

思路: 定义两个游标,一个指向数组头部,另一个指向尾部。左右夹逼,遇到的第一对和为S的两个数,必然乘积最小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        ArrayList<Integer> res = new ArrayList<Integer>();
        int i = 0, j = array.length - 1;
        while(i < j){
            if(array[i] + array[j] > sum) j--;
            else if(array[i] + array[j] < sum) i++;
            else {
                res.add(array[i]);
                res.add(array[j]);
                return res;
            }
        }
        return res;
    }
}

43. 左旋转字符串

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

思路: 有多种解法,假设长度为s,左移n位。

  1. 新建一个辅助字符串,字符串从n位到第s-1位,移到辅助字符串的第0位至第s-n-1位;字符串的第0至n-1位,移至辅助字符串的第s-n位至s-1位,输出辅助字符串。用python就是s[n:] + s[:n]。
  2. 将字符串翻倍,例abcd编程abcdabcd,若左移动2位,则输出翻倍后字符串的第2后长度为s的子串。
  3. 将字符串在第n-1位分割位两个子串,各自反转,再反转整个字符串。例abcdefgh,n为3,各自反转后cbahgfed,整体反转defghabc。

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Solution {
    public String LeftRotateString(String str,int n) {
        if(str.length() == 0) return "";
        n = n % str.length();
        char[] c = str.toCharArray();
        char[] res = new char[str.length()];
        for(int i = n; i < str.length(); i++){
            res[i - n] = c[i];
        }
        for(int i = 0; i < n; i++){
            res[str.length() - n + i] = c[i];
        }
        return String.valueOf(res);
    }
}

解法二:

1
2
3
4
5
6
7
8
public class Solution {
    public String LeftRotateString(String str,int n) {
        if(str.length() == 0) return "";
        n = n % str.length();
        String res = str + str;
        return res.substring(n, str.length() + n);
    }
}

解法三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Solution {
    public String reverse(String str , int left, int right){
        char[] strlist = str.toCharArray();
        int i = left, j = right;
        while(i < j) {
            char tmp = strlist[j];
            strlist[j--] = strlist[i];
            strlist[i++] = tmp;
        }
        return str = String.valueOf(strlist);
    }

    public String LeftRotateString(String str,int n) {
        if(str.length() == 0) return "";
        n = n % str.length();
        str = reverse(str, 0, n - 1);
        str = reverse(str, n, str.length() - 1);
        return reverse(str, 0 , str.length() - 1);
    }
}