将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:
P A H N A P L S I I G Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
解析:
找出按 Z 形排列后字符的规律,然后直接保存起来。
可以看到,图形其实是有周期的,0,1,2 … 7 总共 8 个,然后就又开始重复相同的路径。周期(cycleLen)的计算就是:cycleLen = 2 × 行数(numRows) - 2 = 2 × 5 - 2 = 8 个。
我们发现第 0 行和最后一行一个周期内只有一个字符,所以第一个字符下标是 0 ,第二个字符下标是 0 + cycleLen = 8,第三个字符下标是 8 + cycleLen = 16 。
中间其他行一个周期内都是两个字符。
第 1 个字符和第 0 行的规律是一样的。
第 2 个字符其实就是下一个周期的第 0 行的下标减去当前行。什么意思呢?
我们求一下第 1 行第 1 个周期内的第 2 个字符,下一个周期的第 0 行的下标是 8 ,减去当前行 1 ,就是 7 了。
我们求一下第 1 行第 2 个而周期内的第 2 个字符,下一个周期的第 0 行的下标是 16 ,减去当前行 1 ,就是 15 了。
我们求一下第 2 行第 1 个周期内的第 2 个字符,下一个周期的第 0 行的下标是 8 ,减去当前行 2 ,就是 6 了。
当然期间一定要保证下标小于 字符串长度(n) ,防止越界。
Java实现:
class Solution { public String convert(String s, int numRows) { //如果只有一行,则直接返回字符串 if(numRows == 1){ return s; } //字符串长度 int len = s.length(); //用来存储输出字符串 StringBuilder ret = new StringBuilder(); //计算每个周期字符个数 int cycleLen = 2 * numRows - 2; //遍历行 for(int i=0; i<numRows; i++){ //遍历周期数,每次加一个周期 for(int j=0; j+i<len; j+=cycleLen){ //添加的是每个周期的第一列 ret.append(s.charAt(j + i)); //添加的是 第i行 第j个 周期的第二个字符;即 除去第0行和最后一行 if(i != 0 && i != numRows - 1 && j+cycleLen-i < len){ ret.append(s.charAt(j + cycleLen - i)); } } } return ret.toString(); } }
时间复杂度:O(n),虽然是两层循环,但第二次循环每次加的是 cycleLen ,无非是把每个字符遍历了 1 次,所以两层循环内执行的次数肯定是字符串的长度。
空间复杂度:O(n),保存字符串。
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
解法一:
将整数转为字符串,字符串反转后再转为整数即可,需要主要负数的情况。
class Solution { public int reverse(int x) { String sx = Integer.toString(x); //可能带负号 String s = sx; //存储新的字符串 int flag = 1; //默认正数 //如果x为负数,则需乘 -1,设置flag为-1 if(x<0){ flag = -1; //负数 s = sx.substring(1); //去除负号 } try{ //字符串反转后转整数 x = Integer.valueOf((new StringBuilder(s)).reverse().toString()) * flag; return x; }catch (Exception e){ return 0; } } }
解法二:
将整数不断取余得到个位数,然后再除十去掉个位数;然后用一个变量来存放结果。
class Solution { public int reverse(int x) { long rev = 0; //存值变量 while(x != 0){ int pop = x % 10; //个位数 x /= 10; //取整, 去除个位数 rev = rev * 10 + pop; } //如果超过范围 就返回0 if(rev > Integer.MAX_VALUE || rev < Integer.MIN_VALUE) return 0; return (int)rev; } }
时间复杂度:循环多少次呢?数字有多少位,就循环多少次,也就是 log10(x)+1log_{10}(x) + 1log10(x)+1 次,所以时间复杂度是 O(log(x))。
空间复杂度:O(1)。
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
函数 myAtoi(string s) 的算法如下:
注意:
解析:
主要是考虑好字符的处理方式。
Java实现:
class Solution { public int myAtoi(String s) { int i = 0; int len = s.length(); //字符串长度 int sign = 1; //正数为1,负数为-1 int res = 0; //存储整数 while (i < len && s.charAt(i) == ' ') { //如果字符串前导位置为空格,循环到有数据的那一个位置 i++; } int start = i; //记录一下当前之后所有数据开始的位置 for (; i < len; i++) { int c = s.charAt(i); //当前字符 if (i == start && c == '+') { //判断是否是+,并且+要在初始位置 sign = 1; } else if (i == start && c == '-') { //判断是- sign = -1; } else if (Character.isDigit(c)) { //判断是数字 int num = c - '0'; //如果是数字,其他不用考虑,只需要考虑两种超限的情况 if (res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && num > Integer.MAX_VALUE % 10)) { return Integer.MAX_VALUE; } else if (res < Integer.MIN_VALUE / 10 || (res ==Integer.MIN_VALUE / 10 && -num < Integer.MIN_VALUE % 10)) { return Integer.MIN_VALUE; } //计算整数值 res = res * 10 + sign * num; } else { //如果有一次循环既不是数字,又不是'+'和'-',那么立即退出循环,并返回当前res中已经储存的值 break; } } return res; } }
给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。
解法一:
将整数转为字符串,使用双指针解决。
class Solution { public boolean isPalindrome(int x) { String s = String.valueOf(x); //将整数转为字符串 int left = 0; //左指针 int right = s.length() - 1; //右指针 while(left < right){ if(s.charAt(left) != s.charAt(right)){ //如果左边字符不等于右边字符,不是回文 return false; } left++; //移动左指针 right--; //移动右指针 } return true; } }
解法二:
将右半部分倒置然后和左半部比较就可以了。比如 1221 ,把 21 转置和 12 比较就行了。
class Solution { public boolean isPalindrome(int x) { if(x<0){ return false; } int n = (int) (Math.log(x) / Math.log(10) + 1); //总位数 int rev = 0; //存放右边反转的值 int pop = 0; //存放个位数值 //倒置右半部分 for(int i=0; i<n/2; i++){ pop = x % 10; //个位数 rev = rev * 10 + pop; //计算值 x /= 10; //取整,去除已计算的个位数 } //如果是整数的总位数是偶数 if(n % 2 == 0 && x == rev){ return true; } //如果是整数的总位数是奇数,就将x去除一位再与右边比较 if(n % 2 != 0 && x/10 == rev){ return true; } return false; } }
时间复杂度:循环 x 的总位数的一半次,所以时间复杂度依旧是 O(log(x))。
空间复杂度:O(1),常数个变量。
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
解法一:
递归
class Solution { public boolean isMatch(String s, String p) { //判断当p为空时,如果s也为空,就返回true,否则为false if(p.isEmpty()) return s.isEmpty(); //当s不为空,并且 p与s的第一个字符相等 或 p为'.'时,为true boolean first_match = (!s.isEmpty() && (p.charAt(0) == s.charAt(0) || p.charAt(0) == '.')); //当p长度大于等于2时,才考虑'*' if(p.length() >= 2 && p.charAt(1) == '*'){ //两种情况 //1. p直接跳过两个字符:表示*前面的字符出现0次 //2. p不变,例如 s=aa,p=a* 第一个a匹配,然后s的第二个a接着和 p的第一个a匹配 :表示 * 用前一个字符替代。 return (isMatch(s,p.substring(2))) || (first_match && isMatch(s.substring(1),p)); } else { //递归 return first_match && isMatch(s.substring(1),p.substring(1)); } } }
解法二:
动态规划
class Solution { public boolean isMatch(String text, String pattern) { // 多一维的空间,因为求 dp[len - 1][j] 的时候需要知道 dp[len][j] 的情况, // 多一维的话,就可以把 对 dp[len - 1][j] 也写进循环了 boolean[][] dp = new boolean[2][pattern.length() + 1]; dp[text.length()%2][pattern.length()] = true; // 从 len 开始减少 for (int i = text.length(); i >= 0; i--) { for (int j = pattern.length(); j >= 0; j--) { if(i==text.length()&&j==pattern.length()) continue; boolean first_match = (i < text.length() && j < pattern.length() && (pattern.charAt(j) == text.charAt(i) || pattern.charAt(j) == '.')); if (j + 1 < pattern.length() && pattern.charAt(j + 1) == '*') { dp[i%2][j] = dp[i%2][j + 2] || first_match && dp[(i + 1)%2][j]; } else { dp[i%2][j] = first_match && dp[(i + 1)%2][j + 1]; } } } return dp[0][0]; } }