1. 两数之和

public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> num2IndexMap = new HashMap<Integer,Integer>();
        for(int i=0;i<nums.length;i++){
            //给当前的nums[i]在map里寻找target-nums[i],两个加起来刚好等于target
            if(num2IndexMap.containsKey(target-nums[i])) {
                //将两者的下标返回
                return new int[]{num2IndexMap.get(target-nums[i]), i};
            }
            num2IndexMap.put(nums[i], i);
        }
        return null;
    }

415. 字符串相加

class Solution {
    public String addStrings(String num1, String num2) {
        int i=num1.length()-1;
        int j=num2.length()-1;
        StringBuffer sb = new StringBuffer();
        int res = 0;
        while (i>=0 || j>=0) {
            int sum = res;
            if (i>=0) {
                sum = sum + num1.charAt(i) - '0';
                i--;
            }
            if (j>=0) {
                sum = sum + num2.charAt(j) - '0';
                j--;
            }
            // 进位
            if (sum >= 10) {
                res = 1;
                sum = sum - 10;
            }
            else {
                res = 0;
            }
            sb.append(sum);
        }
        // 最后一个进位。如 1+9 应该输出 10 而不是 0
        if (res>0) {
            sb.append(res);
        }
        return sb.reverse().toString();
    }

}

 

 

2. 两数相加

两数都是用单向链表表示的。(倒序,头节点是最低位)
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
两个数分别求出来再加,会超过 Long 的范围。
因此从最地位(头节点)开始逐位相加,有进位向上一位进位,每次两链表对应位+进位,构造一个结果链表
如果一个链表比另一个长,那么后面就是这个链表位数 + 进位
注意: 最后一位,不断进位的话,可能多出来一位进位

 

 

13. 罗马数字转整数

每次取字符映射的整数相加。对于放在左边相当于 -1 -10 -100 的特殊处理下

class Solution {

    private static Map<Character, Integer> valueMap = new HashMap();

    static {
        valueMap.put('I', 1);
        valueMap.put('V', 5);
        valueMap.put('X', 10);
        valueMap.put('L', 50);
        valueMap.put('C', 100);
        valueMap.put('D', 500);
        valueMap.put('M', 1000);
    }
    public int romanToInt(String s) {

        int result = 0;
        for (int i = 0;i<s.length();i++) {
            int add = valueMap.get(s.charAt(i));
            if (i < s.length() - 1 ) {
                // IV 相当于 -1 再 +5 IX 相当于 -1 再 +10 
                if (s.charAt(i) == 'I' && (s.charAt(i+1) == 'V' || s.charAt(i+1) == 'X')) {
                    add = -1;
                }
                if (s.charAt(i) == 'X' && (s.charAt(i+1) == 'L' || s.charAt(i+1) == 'C')) {
                    add = -10;
                }
                if (s.charAt(i) == 'C' && (s.charAt(i+1) == 'D' || s.charAt(i+1) == 'M')) {
                    add = -100;
                }
            }
            result+=add;
        }
        return result;
    }
}

 

 

67. 二进制求和

class Solution {
    public String addBinary(String a, String b) {

        StringBuffer res = new StringBuffer();
        int an = a.length();
        int bn = b.length();
        int i=0;
        int len = Math.max(an, bn);
        // 余数
        int rem = 0;
        while(i<len) {
            // 如果哪一个字符串先遍历完了,就用 '0' 替代占位
            int aCur = 0;
            if (i<an) {
                // 从后往前
                aCur = a.charAt(an-i-1) - '0';
            }
            int bCur = 0;
            if (i<bn) {
                 // 从后往前
                bCur = b.charAt(bn-i-1) - '0';
            }
            // 当前位为 (a 位 + b 位 + 余数)%2
            int cur = (aCur + bCur + rem)%2;
            // 余数
            rem = (aCur + bCur + rem)/2;
            res.append(String.valueOf(cur));
            i++;
        }
        // 最后最高位如果有进位,别忘了
        if (rem == 1) {
            res.append('1');
        }
        // 将结果反转即可
        return res.reverse().toString();
    }

    public String addBinary2(String a, String b) {

        StringBuffer res = new StringBuffer();
        int an = a.length();
        int bn = b.length();
        int i=0;
        int len = Math.max(an, bn);
        boolean rem = false;
        while(i<len) {
            // 如果哪一个字符串先遍历完了,就用 '0' 替代占位
            char aCur = '0';
            if (i<an) {
                aCur = a.charAt(an-i-1);
            }
            char bCur = '0';
            if (i<bn) {
                bCur = b.charAt(bn-i-1);
            }
            if (aCur == '1' && bCur == '1') {
                if (rem) {
                    rem = true;
                    res.append('1');
                }
                else {
                    rem = true;
                    res.append('0');
                }
            }
            else if (aCur == '0' && bCur == '0') {
                if (rem) {
                    rem = false;
                    res.append('1');
                }
                else {
                    rem = false;
                    res.append('0');
                }
            }
            else {
                // 其中一个为0,一个为 1 的情况
                if (rem) {
                    rem = true;
                    res.append('0');
                }
                else {
                    rem = false;
                    res.append('1');
                }
            }
            i++;
        }
        // 最后最高位如果有进位,别忘了
        if (rem) {
            res.append('1');
        }
        // 将结果反转即可
        return res.reverse().toString();
    }
}

 

34. 在排序数组中查找元素的第一个

先用二分查找法找 target 出现的任意一个位置

由于是非递减排序数组,所以 target 出现的位置都是挨着的,连续的

再由前面的二分查找找到的位置,一直向左找还有没有和 target 相等的;一直向右找还有没有 target 相等的

class Solution {
    public int[] searchRange(int[] nums, int target) {

        // 先二分查找有没有 target
        int leftIndex = 0;
        // 注意 rightIndex = nums.length-1;
        int rightIndex = nums.length-1;
        int midIndex = 0;
        boolean hasRes = false;

        //注意 这里是 <= 不是 < 。这样可以适配像 [1] target=1 这样只有一个元素的情况
        while(leftIndex <= rightIndex) {
            midIndex = (leftIndex + rightIndex)/2;
            if (nums[midIndex] == target) {
                hasRes = true;
                break;
            }
            else if (nums[midIndex] > target) {
                rightIndex = midIndex-1;
            }
            else {
                leftIndex = midIndex+1;
            }
        }

        // 没有 target 的情况
        if (!hasRes) {
            return new int[] {-1, -1};
        }
        
        // 再由找到的位置,向左向右找有没有相同的值
        int start = midIndex;
        int end =midIndex;
        while(start > 0 && nums[start - 1] == target) {
            start--;
        }
        while(end <nums.length-1 && nums[end + 1] == target) {
            end++;
        }
        return new int[] {start, end};
    }
}

 

69. x 的平方根

要求最后返回平方根的整数部分(舍弃小数部分)

所以返回的数的平方一定比x小或等于,它的 + 1 的平方一定比x大于

class Solution {
    public int mySqrt(int x) {

        int left = 0;
        int right = x;
        int mid  = 0;
        while(left<=right) {
            mid = (left+right)/2;
            // 最后会舍弃整数部分,所以返回的数的平方一定比x小或等于,它的 + 1 的平方一定比x大
            if (Math.pow(mid, 2) <= x && Math.pow(mid+1, 2) > x) {
                return mid;
            }
            else if (Math.pow(mid, 2) < x) {
                left = mid+1;
            }
            else {
                right = mid - 1;
            }
        }
        return mid;

    }
}

 

 

179. 最大数

 

自定义比较方法,34 和 3 ,比较是 343 大还是 334 大,以这个比较结果进行排序,即可得到最后排序过的数组

再把排序后的数组拼接起来。证明方法可以看题解。  

class Solution {
    public String largestNumber(int[] nums) {

        // int 类型不支持 Arrays.sort 自定义比较方法。必须是包装类型,所以先转为 String
        String[] numstr = new String[nums.length];
        for (int i=0;i<nums.length;i++) {
            numstr[i] = nums[i]+"";
        }

        // 重写比较方法
        Arrays.sort(numstr, (b, a) -> {
            return (a+""+b).compareTo(b+""+a);
        });

        // 最后要以拼接好的字符串返回
        StringBuilder sb = new StringBuilder();
        boolean b = true;
        for (int i=0;i<numstr.length;i++) {
            // 处理 [0,0,0] 这种情况最后要返回 0 而不是 000
            if (b && "0".equals(numstr[i]) && numstr.length - i > 1) {
                continue;
            }
            b = false;
            sb.append(numstr[i]);
        }    
        return sb.toString();
    }
}

 

 

 1419. 数青蛙

用一个 Map 来维护每个字母的计数

c -> r  则 c 的计数 -1,r 的计数 +1

r -> o 则 r 的计数 -1,o 的计数 +1

o -> a 则 o 的计数 -1,a 的计数 +1

a -> k 则 a 的计数 -1,k 的计数 +1

  • 每次有新的 c 过来,k 的计数不用 -1,只把 c 的计数 +1 ,并且  nowFrogCnt +1 (表示现在一共有多少只青蛙共存)
  • 每次到 k ,说明有一只青蛙叫结束了,nowFrogCnt -1

结果 res 就是 nowFrogCnt 的最大值,通过每次循环里面比较得出

最后遍历 Map ,除了 k 之外,如果还有字母的计数不为 1 ,说明不是有效组合,返回 -1

class Solution {

    private static List<Character> frogsList = Arrays.asList('c', 'r', 'o', 'a', 'k');
    
    private Map<Character, Integer> char2CntMap;

    public Solution() {
        char2CntMap = new HashMap();
        char2CntMap.put('c', 0);
        char2CntMap.put('r', 0);
        char2CntMap.put('o', 0);
        char2CntMap.put('a', 0);
        char2CntMap.put('k', 0);
    }

    public int minNumberOfFrogs(String croakOfFrogs) {
        char[] frogArray = croakOfFrogs.toCharArray();
         // 现在有多少只青蛙
        int nowFrogCnt = 0;
        int res = 0;
        for (int i=0;i<frogArray.length;i++) {
            char thisChar = frogArray[i];
            int thisCharCnt = char2CntMap.get(thisChar);
            if (thisChar == 'c') {
                char2CntMap.put(thisChar, ++thisCharCnt);
                // 新增一只青蛙
                nowFrogCnt++;
            }
            else {
                char preChar = getPre(thisChar);
                int preCharCnt = char2CntMap.get(preChar);
                if (preCharCnt == 0) {
                    return -1;
                }
                char2CntMap.put(preChar, --preCharCnt);
                char2CntMap.put(thisChar, ++thisCharCnt);
                if (thisChar == 'k') {
                    // 一只青蛙叫完了
                    nowFrogCnt--;
                }
            }
            // 统计正在叫的青蛙数达到的最大值
            if (nowFrogCnt > res) {
                res = nowFrogCnt;
            }
        }
        for (Map.Entry<Character, Integer> entry : char2CntMap.entrySet()) {
            if (entry.getKey() != 'k' && entry.getValue() != 0) {
                return -1;
            }
        }
        return res;
    }


    private char getPre(char now) {
        // "crcoakroak" c 的前一个不能为 k 的例子
        // 可能第一个没叫完第二个就开始了。这样 k 可能为 -
        // if (preCharCnt == 0) {
        //           return -1;
        //       }
        // 的条件就没法用了
        if (now == 'c') {
            return '-';
        }
        else if (now == 'r') {
            return 'c';
        }
        else if (now == 'o') {
            return 'r';
        }
        else if (now == 'a') {
            return 'o';
        }
        else if (now == 'k') {
            return 'a';
        }
        return '-';
    }


}

别人的绝妙方法

遍历蛙鸣串,记录每个字母的频数,c表示'c'的频数,r表示'r'的频数,o表示'o'的频数,a表示'a'的频数,k表示'k'的频数。

一方面,由于蛙鸣的字母是按照croak的顺序出现的,所以在遍历的过程中必须要满足c>=r>=o>=a>=k,并且遍历完成后必须满足c=r=o=a=k(每个蛙鸣必须是完整的),否则就是不合法的,返回-1。

另一方面,我们其实可以把蛙鸣看成是若干区间进行重叠,遍历到croakOfFrogs的每个位置都会有一个“各个字母的频数”状态,所有位置的状态中“并行度”最大的地方就是最少需要的青蛙数量。由于k是一次蛙鸣的结尾,c是一次蛙鸣的开头,且c>=k,所以某个位置的“并行度”其实就是c-k,维护这个最大值即可。

public int minNumberOfFrogs(String croakOfFrogs) {
        int c=0,r=0,o=0,a=0,k=0;
        int ans = 0;
        char[] charArr = croakOfFrogs.toCharArray();
        for (char thischar : charArr) {
            if (thischar == 'c') c++;
            if (thischar == 'r') r++;
            if (thischar == 'o') o++;
            if (thischar == 'a') a++;
            if (thischar == 'k') k++;
            // 必须满足先后顺序,所以要 c>=r>o>a>k
            if (!(c>=r && r>=o && o>=a && a>=k)) return -1;
            // c-k 就是当前并发进行的青蛙的数量,求出这个并发的最大值
            ans = Math.max(ans, c-k);
        }
        // 最后所有的计数要相等
        return c==r && r==o && o==a && a==k ? ans : -1;
   }

 

 

238. 除自身以外数组的乘积

class Solution {
    /*
    原数组:   [1       2       3       4]
左部分的乘积:   1       1      1*2    1*2*3
右部分的乘积: 2*3*4    3*4      4      1
结果:        1*2*3*4  1*3*4   1*2*4  1*2*3*1
    */
    // 从上面的图可以看出,当前位置的结果就是它左部分的乘积再乘以它右部分的乘积。
// 因此需要进行两次遍历,第一次遍历用于求左部分的乘积,
// 第二次遍历在求右部分的乘积的同时,再将最后的计算结果一起求出来。
public int[] productExceptSelf(int[] nums) { int[] res = new int[nums.length]; int pre1 = 1; for (int i=0;i<nums.length;i++) { res[i] = pre1; pre1 = pre1*nums[i]; } int pre2 = 1; for (int i=nums.length-1;i>=0;i--) { res[i] = res[i]*pre2; pre2 = pre2*nums[i]; } return res; } }

 

41. 缺失的第一个正数

解法一:

 最后的答案一定是 <= n+1 的一个正整数。

因为数组的每一个位置都按序放不同的正整数,那么结果就是 n+1。比如 [5,2,3,1,4] 结果就是 6

但实际情况多为还跳跃了一些,放了别的数,所以最后的结果是 <n+1

所以用数组元素是否为负,来表示这个取值为这个下标的元素是否在数组里出现过。比如上图最后的结果 [-3,4,-7,-1,9,7] 第 0 个元素取值为负,说明 1 在数组里出现过;第 2 个元素取值为正,说明 3 在数组中出现过;第 3 个元素取值为负,说明 4 在数组中出现过。

解法二(我的,时间复杂度应该不够 O(n)):

结果的初始值为最小的正整数 1

  • 如果数字出现过,就加到 set 里
  • 如果数字 == res,说明这个 res 出现过,那么 res 就要继续往上加,加到一个没出现过的正整数(不在 set 里)
class Solution {
    public int firstMissingPositive(int[] nums) { 
        // 最后的答案一定是 <= n+1
        // 因为数组的每一个位置都按序放不同的正整数,那么结果就是 n+1
        // 但实际情况多为还跳跃了一些,放了别的数,所以最后的结果是 <n+1
        int n = nums.length;
        // 1.将所有负数变为 n+1 (负数都不可能是答案)
        for (int i=0;i<n;i++) {
            if (nums[i] <= 0) {
                nums[i] = n+1;
            }
        }
        // 2.将所有 <=n 的数,将其对应位置的数变为 负(最后如果为负,对于 1~n 的正整数,这些出现过)
        // 注意 [2,1] 第一次循环变为 [2,-1] 第二次循环到了 -1 这里,-1 不可能作为下标,所以要取绝对值
        for (int i=0;i<n;i++) {
            int valueAbs = Math.abs(nums[i]);
            if (valueAbs <= n) {
                // 为什么要 -Math.abs(nums[valueAbs-1]) 加上绝对值符号
                // 因为比如 [1,1] 第一次循环变为 [-1,1] 第二次循环就不能直接加 - ,否则就成了 [1,1]
                nums[valueAbs-1] = -Math.abs(nums[valueAbs-1]);
            }
        }
        // 3.遍历数组,找到第一个不为负的数,其下标就是没出现过的最小正整数
        for (int i=0;i<n;i++) {
            if (nums[i] > 0) {
                // 因为下标是从 0 开始的,而正整数是从1开始的,所以最后要 + 1
                return i+1;
            }
        }
        // [3,2,1] 最后变为 [-3,-2,-1],那么结果就是 4
        return n+1;
    }

    public int firstMissingPositive2(int[] nums) { 

        int res = 1;
        Set<Integer> set = new HashSet();
        for (int i=0;i<nums.length;i++) {
            if (nums[i] > res) {
                set.add(nums[i]);
            }
            else if (nums[i] == res) {
                // 之前已经出现过的话,继续往上加,加到一个没出现过的
                while (set.contains(res + 1)) {
                    res++;
                }
                res++;
            }
        }
        return res;
    }

}

 

 

287. 寻找重复数

 由于 n+1 个位置,n个不同元素。大部分都是单向的链表这样下去。

重复元素一定会指向一个元素,而重复元素会被多于一个元素指向:一个在它的前面指向它,一个在它的后面回过头来指向它,这就形成了环

看作图,那么重复元素就是入环点,等同于求入环点:

  • 慢指针走一步,快指针走两步,如果快慢指针相遇说明有环
  • 快慢指针相遇后,令一个新指针指向链表头,和慢指针同步走一步,最后这个新指针和慢指针会在入环点相遇
class Solution {
    public int findDuplicate(int[] nums) {
        int slow=0;
        int fast=0;
        // 慢指针走一步,快指针走两步。相遇的话说明有环
        do {
            slow = nums[slow];
            fast = nums[nums[fast]];
        }
        // 用 do...while 的原因是
        // 一开始 slow 和 fast 都是0,如果 while(slow!=fast) 的话就进入不了条件了
        while (slow != fast);

        // 相遇后,另外一个新指针指向头部。和现在的慢指针同时移动一步
        // 最后会在入环点相遇
        int third = 0;
        while (slow != third) {
            third = nums[third];
            slow = nums[slow];
        }
        return slow;
    }
}