java算法之剑指offer题目整理1-数字和字符串篇

数字处理

1. 剑指 Offer 56 - I. 数组中数字出现的次数

class Solution {

    public int[] singleNumbers(int[] nums) {

        int n = 0, m = 1, x = 0, y = 0;  // 任何数与0异或,都是其本身

        for(int num: nums){  // 获取整个数组异或的结果

            n ^= num;

        }

        while((m & n) == 0){  // 与1与的结果都是该数本身

            m <<= 1;  // 左移一位,找到n中由于x和y不一样而导致的二进制结果为1的那位

        }

        for(int num: nums){

            if((num & m) == m) x ^= num;

            else y ^= num;

        }

        return new int[]{x, y};

    }

}

/* 写的很不错的解体思路:

https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/solution/jian-zhi-offer-56-i-shu-zu-zhong-shu-zi-tykom/

*/

 

2. 剑指 Offer 15. 二进制中1的个数

public class Solution {

    // you need to treat n as an unsigned value

    public int hammingWeight(int n) {

        int res = 0, tmp = 1;

        for(int i = 0; i < 32; i++){

            if((tmp & n) != 0) res++;

            tmp <<= 1;

        }

        return res;

    }

}

 

3.  剑指 Offer 16. 数值的整数次方

// 实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

class Solution {

    public double myPow(double x, int n) {

        int f = 0;

        double res = 1.0;

        long b = n;

        if(n < 0) {

            f = 1;

            b = -b;

        }

        if(n == 0) return 1.0; 

        // while(n > 0){

        //     res = res * x;

        //     n--;

        // }

        while(b > 0){

            if((b & 1) == 1) res *= x;

            x *= x;

            b >>= 1;

        }

        if(f == 1) res = 1 / res;

        return res;

    }

}

/*  解题思路:

https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/solution/mian-shi-ti-16-shu-zhi-de-zheng-shu-ci-fang-kuai-s/

*/

 

剑指 Offer 66. 构建乘积数组    动态规划求解

class Solution {

    public int[] constructArr(int[] a) {

        int size = a.length;

        if(size == 0) return new int[0];

        int dp[] = new int[size]; // 存储最终的结果

        dp[0] = 1;

        for(int i = 1; i < size; i++){ // 先将i左边的数相乘

            dp[i] = dp[i-1] * a[i-1];

        }

        int tmp = 1;

        for(int i = size-2; i >= 0; i--){  // 再将右边的数相乘

            tmp = tmp * a[i+1];

            dp[i] *= tmp;

        }

        return dp;

    }

}

/*
采用本算法,时间复杂度可以降至O(2n),但是如果采用暴力求解,则时间复杂度为O(n^2)
*/

 

 5. 剑指 Offer 49. 丑数    动态规划求解

// 动态规划求解,时间复杂度和空间复杂度均为O(n)

class Solution {
    public int nthUglyNumber(int n) {
        int dp[] = new int[n];
        int a = 0, b = 0, c = 0;  // a指针代表2,b指针代表3,c指针代表5
        dp[0] = 1;
        for(int i = 1; i < n; i++){
            dp[i] = Math.min(Math.min(dp[a]*2, dp[b]*3), dp[c]*5 );    // dp[i]中存放的是第i+1个丑数
            if(dp[i] == dp[a]*2) a++;
            if(dp[i] == dp[b]*3) b++;
            if(dp[i] == dp[c]*5) c++;
        }
        return dp[n-1];
    }
}

/*
简单的理解就是,依据现有的求解结果,将每个结果分别乘上2,3,5,扩展解空间,而不是采用暴力,搜索整个可能的空间。暴力搜索的时间复杂度为O(n^2)
*/

 

6. 剑指 Offer 45. 把数组排成最小的数

class Solution {
    public String minNumber(int[] nums){
        int size = nums.length;
        String str[] = new String[size];
        for(int i = 0; i < size; i++){
            str[i] = String.valueOf(nums[i]);
        }
        Arrays.sort(str, (x, y) -> (x+y).compareTo(y+x));  // 将两个数拼接起来然后再比较,前后两个字符串的asc码的差值,前减后。默认从小到大排序,(y+x).compareTo(x+y)则会从大到小排序。
        String res = "";
        for(int i = 0; i < size; i++)
            res += str[i];
        return res;
    }
}
/*
compareTo返回值是整型,它是先比较对应字符的大小(ASCII码顺序),如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的长度差值,如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较,以此类推,直至比较的字符或被比较的字符有一方结束。
// https://www.runoob.com/java/java-string-compareto.html
*/
/* 思路:对数组进行排序,排序依据每个数字的最高位,如果两个数字最高位相同,就比较次高位,依次类推。 X思路有问题, 120、12、1这组数据就行不通 正确的解题思路是将待比较的两个数字转化为字符串,然后将其相加,再比较这两个数的大小,如120和12,12012 < 12120 */

 

6. 剑指 Offer 44. 数字序列中某一位的数字

数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。  // 从第0位开始

请写一个函数,求任意第n位对应的数字。

class Solution {    
    public int findNthDigit(int n) {
        long count = 9, start = 1;  // count记录x位数有多少个
        int digit = 1;  // digit == x,x为几位数
        while(n > count){    // 未考虑值为0的情况
            n -= count;
            start *= 10;
            digit++;
            count = 9 * start * digit;  // count表示所有的x位的数一共有多少个数字,所以需要乘上digit,该表达式可能会超过int的最大值
        }
        long num = ((n-1) / digit) + start;  // 哪个数字,当n=1的时候,就是start。之所以要n-1,是由于n从1开始,如果不-1,n=3 start = 100的时候,指向的不是0,而是101的第一位。
        int tmp = (n-1) % digit;  // 第几位数。
        int res;
        String s = Long.toString(num);
        return Long.toString(num).charAt(tmp) - '0';  // 将一个x位的数变为字符串,然后从中取出第tmp位的数字
    }
}
/*
解题思路:先确定n所对应的数字是多少位数,然后确定是哪个数,最后确定是这个数中的哪个数字
*/

 

7. 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。  

输入: [7,5,6,4]
输出: 5
class Solution {
    int count;
    public void merge(int nums[], int l1, int r1, int l2, int r2){
        int i = l1, j = l2, index = 0;  // index是temp中存放元素的下标
        int temp[] = new int[r2 - l1 + 1];
        while(i <= r1 && j <= r2){
            if(nums[i] > nums[j]){  // 元素相同的时候先放前面的,稳定排序
                temp[index++] = nums[j++];
            }else{  // count加上后面已经空缺的,相当于是比当前小的元素都已经排序好了
                temp[index++] = nums[i++];
                count += j - l2;
            }
        }
        while(i <= r1){
            temp[index++] = nums[i++];
            count += j-l2;
        }
        while(j <= r2){
            temp[index++] = nums[j++];
        }
        for(i = 0; i < index; i++){
            nums[l1+i] = temp[i]; 
        }
    }
    public void mergeSort(int nums[], int left, int right){
        int count = 0;
        if(left < right){
            int mid = (left + right)/2;
            mergeSort(nums, left, mid);
            mergeSort(nums, mid + 1, right);
            merge(nums, left, mid, mid +1, right);
        }
    }
    public int reversePairs(int[] nums) {
        int size = nums.length;
        if(size <= 1){
            return 0;
        }
        mergeSort(nums, 0, size-1);
        return count;
    }
}
/*
依据排序过程中的元素的依次比较来判断逆序数的个数,两个元素位置互换则这两个数必然构成逆序数。冒泡排序过程中的移动次数就可以代表逆序数的个数,但是
冒泡排序的时间是O(n^2), 会超时,所以不行,可以通过归并排序来实现,归并是可以的,每个元素之间都会依次比较,但是快速排序不行,因为快速排序只与首元
素比较就交换了位置,无法知道被交换位置的元素到底比多少个元素大。
问题:当数据是4 3 3 2的时候,结果应该是5,但是归并排序的统计结果为4???
*/

 

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

输入:n = 12
输出:5
class Solution {
    public int countDigitOne(int n) {
        int digit = 1, res = 0;  // digit代表当前cur是第几位
        int cur = n % 10, high = n / 10, low = 0;  // 第一次cur指向个位,low无位,所以赋值为0
        while(high != 0 || cur != 0){  // 当high为0的时候,说明cur指向了最高位;当cur 为0的时候,high不为0,也无法结束;只有当两者都为0的时候才能结束
            if(cur == 0) res += high * digit;
            else if(cur == 1) res += high * digit + low + 1;    // +1是由于高位可能位全0
            else res += (high + 1) * digit;

            low += cur*digit;
            cur = high % 10;
            high = high / 10;
            digit *=10;
        }
        return res;
    }
}

 

    // 在high变动的时候,low也会变动,乘10是由于cur和low组成的数可以位10-19,一共有10位​。 而用23的原因是00-22,一共23个可能。所以23*10。

    // ​cur位不动,能动的就是low和high组成的数,其范围在000-234之间,所以,一共有235种可能

 

    // 与上面一种的区别是,2315-2319这5个是新的。000-239,一共240种可能。

 

 

 

字符串处理

1. 剑指 Offer 67. 把字符串转换成整数    细节处理比较麻烦

class Solution {

    public int strToInt(String str) {

        int symbol = 1, f = 0;

        char chs[] = str.trim().toCharArray();

        int size = chs.length;

        if(size == 0) return 0;

        int res = 0, j = 0;

        int maxv = Integer.MAX_VALUE / 10;

        if(chs[0] == '-'){

            symbol = -1;

            j = 1;

        }else if(chs[0] == '+'){

            j = 1;

        }

        /*

        注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

        */

        for(int i = j; i < size; i++){

            if(chs[i] > '9' || chs[i] < '0'){

                break;

            }

            if(res > maxv || res == maxv && chs[i] > '7')     // 对于超出Integer.MAX_VALUE的值的处理是关键

                return symbol == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;  

            res = res * 10 + ( chs[i] - '0' );

        }

        return res*symbol;

    }

}

 

 2. 剑指 Offer 48. 最长不含重复字符的子字符串    动态规划

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        char chs[] = s.toCharArray();
        int size = chs.length;
        int dp[] = new int[size];  
        // dp[j] 代表以字符 s[j]s[j] 为结尾的 “最长不重复子字符串” 的长度。
        int maxv = 1;
        if(size == 0) return 0;
        dp[0] = 1;
        Map<Character, Integer> maps = new HashMap<Character, Integer>();
        maps.put(chs[0], new Integer(0));  // 字符在数组中的下标
        for(int i = 1; i < size; i++){
            if( maps.get(chs[i]) == null ){  // 之前没有出现过该字符
                dp[i] = dp[i-1]+1;
            }else{  // 之前出现过该字符
                int tmp = maps.get(chs[i]).intValue(); 
                if(i - tmp > dp[i-1]){  // 最近的相同的字符在dp[i-1]的范围之外
                    dp[i] = dp[i-1] + 1;  // abcddescaui, a的情况
                }else{
                    dp[i] = i - tmp;  // 最近的相同的字符在dp[i-1]的范围之中
                }
            }
            maps.put(chs[i], new Integer(i));    // 只有一个hashmap,后出现的字符及其位置会替换之前相同的字符及其位置
            if(dp[i] > maxv) maxv = dp[i];
        }
        return maxv;
    }
}

 

 

3. 剑指 Offer 46. 把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
class Solution {
    public int translateNum(int num) {
        char chs[] = String.valueOf(num).toCharArray();
        int size = chs.length;
        int dp[] = new int[size];  // 截止第i位,一共可以有多少种组合方式
        dp[0] = 1;
        if(size >= 2){    // 获取dp[1]的值
            String tmp = "" + chs[0] + chs[1];
            int n = Integer.valueOf(tmp).intValue();  // String => Integer => int
            if(n > 9 && n <= 25){  // 02不算字符  // 9 < n <= 25 傻叉表达式!!!!
                dp[1] = 2;
            }else{
                dp[1] = 1;
            }
        }else{
            return 1;
        }
        for(int i = 2; i < size; i++){
            String tmp = "" + chs[i-1] + chs[i];
            int n = Integer.parseInt(tmp);
            if(n > 9 && n <= 25){   // 两个数字结合必定为两位数,在10-25之间
                dp[i] = dp[i-1] + dp[i-2];    // 26421546225,25是z,5是e,用下面想法来理解
            }else{
                dp[i] = dp[i-1];    // 当前数字和前面的数字组成的数如果无法成为一个字符,那么就相当于在原来的每一种可能后面添加了一个字符,数量不会增加
            }

        }
        return dp[size-1];
    }
}

/*
题意是最多有多少种翻译方法,所以可以使用动态规划,就是状态转移方程有点难写。
*/

 



posted @ 2021-05-06 20:58  ybxmCnblogs  阅读(142)  评论(0)    收藏  举报