双指针
验证回文串
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。
字母和数字都属于字母数字字符。
给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。
我的代码:
Java
class Solution {
public boolean isPalindrome(String s) {
// 直接使用双指针指向即可
// 忽略其中的非字母字符
int left = 0;
int right = s.length() - 1;
while(left <= right){
// 获得left的第一个字母字符
while(left <= right && !isLetterOrDigit(s.charAt(left))) left++;
// 获得right的第一个字母字符
while(right >= left && !isLetterOrDigit(s.charAt(right))) right--;
if(left <= right){
if(equalLetterWithoutCase(s.charAt(left),s.charAt(right))){
left++;
right--;
continue;
}else{
return false;
}
}
}
return true;
}
// 判断c是不是字母
private boolean isLetterOrDigit(char c){
if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
return true;
else
return false;
}
// 比较字母是否相等,忽略大小写
private boolean equalLetterWithoutCase(char c1, char c2){
// ASCII中,大写的ASCII值更小
// 都转换为大写进行比较
if(c1 >= 'a') c1 -= 'a' - 'A';
if(c2 >= 'a') c2 -= 'a' - 'A';
return c1 == c2;
}
}
也可以直接使用Java封装好的Character类
Java
class Solution {
public boolean isPalindrome(String s) {
// 直接使用双指针指向即可
// 忽略其中的非字母字符
int left = 0;
int right = s.length() - 1;
while(left < right){
while(left < right && !Character.isLetterOrDigit(s.charAt(left))){
left++;
}
while(right > left && !Character.isLetterOrDigit(s.charAt(right))){
right--;
}
if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))) {
return false;
}
left++;
right--;
}
return true;
}
}
这里有一个需要注意的点,其实不用写等于,因为自己和自己肯定是相等的,而且不写等于还去除了越界的风险
判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
Java
class Solution {
public boolean isSubsequence(String s, String t) {
// 依旧采用双指针即可
// 一个指针指向s,一个指向t
// 如果匹配,则都往后移动
// 如果不匹配,则移动t
// 如果s移到末尾了,则匹配
// 如果t移到末尾了s还没到末尾,则不匹配
if(s.length() == 0){
return true;
}
int sp = 0;
int tp = 0;
while(tp < t.length()){
if(s.charAt(sp) == t.charAt(tp)){
sp++;
tp++;
}else{
tp++;
}
if(sp == s.length()){
return true;
}
}
return false;
}
}
两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
我的解法:
Java
class Solution {
public int[] twoSum(int[] numbers, int target) {
// 重要的点在于,数组是有序的
// 那么使用两个指针,比较指针之和的值与traget值
// 如果大于target,则右指针向左移,以减小值
// 如果小于target,则左指针向右移,以增大值
// 如果等于则返回
int[] result = new int[2];
int left = 0;
int right = numbers.length - 1;
while(left < right){
if(numbers[left] + numbers[right] > target){
right--;
}else if(numbers[left] + numbers[right] < target){
left++;
}else{
result[0] = left+1;
result[1] = right+1;
return result;
}
}
// 没找到
return new int[]{0,0};
}
}
盛最多水的容器
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
我的解答:
Java
class Solution {
public int maxArea(int[] height) {
// 依旧是短板效应
// 使用left和right双指针
// 假设left是其中较矮的一个
// 则right换成 left与right 中的任何一个柱子都不会使储水量增加
// 故逻辑为不断移动较矮的那根柱子,然后记录最大的水量并返回
int n = height.length;
if(n <= 1){
return -1;
}
int left = 0;
int right = n - 1;
int maxWater = 0;
int tempWater = 0;
while(left < right){
if(height[left] < height[right]){
tempWater = height[left] * (right - left);
left++;
}else{
tempWater = height[right] * (right - left);
right--;
}
if(tempWater > maxWater){
maxWater = tempWater;
}
}
return maxWater;
}
}
三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
我的解答:
Java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// 一个很朴素的想法就是固定两个,然后去遍历第三个
// 这样的复杂度是o(n^3)
List<List<Integer>> result = new ArrayList<List<Integer>>();
int n = nums.length;
if(n < 3){
return result;
}
for(int i = 0; i < n - 1; i++){
for(int j = n - 1; j > i ; j--){
int tempSum = nums[i] + nums[j];
for(int k = i + 1; k < j; k++){
if(tempSum + nums[k] == 0){
result.add(Arrays.asList(nums[i],nums[k],nums[j]));
}
}
}
}
return result;
}
}
这样不仅运行效率慢,还存在重复的问题
新的解答:
Java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// 应该充分利用"两数之和"这道题的思路
// i 指针不断遍历,然后去寻找等于 -nums[i]的值
// 先进行排序
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<List<Integer>>();
int n = nums.length;
if(n < 3){
return result;
}
for(int i = 0; i < n - 2; i++){
// 只要i不相同,则i,j,k整体也不相同
if(i > 0 && nums[i] == nums[i-1]){
continue;
}
int left = i + 1;
int right = n - 1;
while(left < right){
if(nums[left] + nums[right] + nums[i] > 0){
// 有序数组,则左移right使得值变小
right--;
}else if(nums[left] + nums[right] + nums[i] < 0){
// 右移left使得值变大
left++;
}else{
// 找到一对了
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 这里还有一个关键的地方,我们还应该跳过left和right的重复值
while(left < right && nums[left] == nums[left+1]){
left++;
}
while(right > left && nums[right] == nums[right-1]){
right--;
}
left++;
right--;
}
}
}
return result;
}
}
还有两个小优化
Java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// 应该充分利用"两数之和"这道题的思路
// i 指针不断遍历,然后去寻找等于 -nums[i]的值
// 先进行排序
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<List<Integer>>();
int n = nums.length;
if(n < 3){
return result;
}
for(int i = 0; i < n - 2; i++){
// 只要i不相同,则i,j,k整体也不相同
if(i > 0 && nums[i] == nums[i-1]){
continue;
}
// 优化1,如果当前的i和紧接着的两个数都大于0了,则后面的肯定也是大于0
if(nums[i] + nums[i+1] +nums[i+2] > 0 ){
break;
}
// 优化2,如果当前的i和最大的两个数都小于0了,那么中间的肯定也是小于0
if(nums[i] + nums[n-1] + nums[n-2] < 0 ){
continue; // i增大可能还会找到合适的值,注意这里不是break
}
int left = i + 1;
int right = n - 1;
while(left < right){
if(nums[left] + nums[right] + nums[i] > 0){
// 有序数组,则左移right使得值变小
right--;
}else if(nums[left] + nums[right] + nums[i] < 0){
// 右移left使得值变大
left++;
}else{
// 找到一对了
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 这里还有一个关键的地方,我们还应该跳过left和right的重复值
while(left < right && nums[left] == nums[left+1]){
left++;
}
while(right > left && nums[right] == nums[right-1]){
right--;
}
left++;
right--;
}
}
}
return result;
}
}