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/ */

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; } }

// 实现函数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/ */
4 剑指 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) */
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 */
数字以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; } }
给定一个数字,我们按照如下规则把它翻译为字符串: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]; } } /* 题意是最多有多少种翻译方法,所以可以使用动态规划,就是状态转移方程有点难写。 */

浙公网安备 33010602011771号