HashMap
存储数组中的数及其下标,遍历查找哈希表中是否存在目标值减当前值,注意两数下标应不同。
class Solution {
public int[] twoSum(int[] nums, int target) {
//使用HashMap存储数及下标
Map<Integer, Integer> map = new HashMap<>();
//遍历查找哈希表中是否存在目标值减当前值
for(int i = 0; i < nums.length; i++){
if(map.containsKey(target - nums[i])){
return new int[]{i, map.get(target - nums[i])};
}
map.put(nums[i], i);
}
//没找到
return new int[2];
}
}
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//直接在l1上修改
//先判断两个链表的长度
int leng1 = 0;
ListNode temp1 = l1;
while(temp1 != null){
leng1 += 1;
temp1 = temp1.next;
}
int leng2 = 0;
ListNode temp2 = l2;
while(temp2 != null){
leng2 += 1;
temp2 = temp2.next;
}
//将l1设为较长的链表
if(leng1 < leng2){
ListNode temp = l1;
l1 = l2;
l2 = temp;
}
ListNode head = new ListNode(0, l1); //虚拟头节点
ListNode pre = null; //最后一位进位需要新建节点
int c = 0; //进位
while(l1 != null){
//相加
if(l2 != null){
l1.val += l2.val + c;
}else{
l1.val += c;
}
//判断是否进位
if(l1.val > 9){
c = 1;
l1.val %= 10;
}else{
c = 0;
}
//下一节点
pre = l1;
l1 = l1.next;
if(l2 != null) l2 = l2.next;
}
//最后一位进位
if(c == 1){
pre.next = new ListNode(1);
}
return head.next;
}
//优化
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//直接在l1上修改
ListNode head = new ListNode(0, l1); //虚拟头节点
ListNode pre = null; //最后一位进位需要新建节点
int c = 0; //进位
while(l1 != null || l2 != null){
//相加,若节点为null则加数为0,否则为节点值
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
int sum = x + y + c;
c = sum / 10; //判断是否进位
if(l1 == null){
pre.next = new ListNode(sum % 10, null);
}else{
l1.val = sum % 10;
}
//下一节点
pre = l1 == null ? pre.next : l1;
if(l1 != null) l1 = l1.next;
if(l2 != null) l2 = l2.next;
}
//最后一位进位
if(c == 1){
pre.next = new ListNode(1);
}
return head.next;
}
}
HashMap
存储字符串中字符最近一次出现的位置(下标),定义左右指针计算子串长度,左指针指向子串左边界,右指针向前移动更新map
、右边界和最大值,若元素已出现过则更新左指针位置。
class Solution {
public int lengthOfLongestSubstring(String s) {
//定义HashMap存储字符串中字符最近一次出现的位置(下标)
Map<Character, Integer> map = new HashMap<>();
//双指针
int left = -1;
int right = 0;
int res = 0;
while(right < s.length()){
if(map.containsKey(s.charAt(right))){ //已经出现过
left = Math.max(left, map.get(s.charAt(right))); //只能向前移动
}
res = Math.max(res, right - left); //更新最大值
map.put(s.charAt(right), right); //更新map
right++; //右指针向前移动
}
return res;
}
}
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
//计算数组总长度
int m = nums1.length;
int n = nums2.length;
int sum = m + n;
int mid1 = sum / 2;
int mid2 = sum / 2;
if(sum % 2 == 0){ //判断中位数为两个数还是一个数
mid1--;
}
//双指针分别遍历左右数组
int count = 0; //计数
int left = 0;
int right = 0;
int res = 0;
while((left < m || right < n) && count <= mid2){
//其中一个数字遍历完成则设为最大值
int n1 = left < m ? nums1[left] : Integer.MAX_VALUE;
int n2 = right < n ? nums2[right] : Integer.MAX_VALUE;
if(count == mid1 || count == mid2){ //计数达到中位数时返回较小的那个
res += n1 < n2 ? n1 : n2;
}
//移动左右指针
if(n1 < n2){
left++;
}else{
right++;
}
count++;
}
if(mid1 == mid2) return res / 1.0;
return res / 2.0;
}
}
k = (m + n) / 2
,求第k小的数,可以两个数组都取k/2
的位置(数组不够长则直接指向末尾),比较两个数组对应位置上值的大小,比较小的数组该位置左边可以舍弃,然后更新k -=舍弃个数
,重复以上步骤,直到k = 1
,此时求两个数组对应位置上较小值即为中位数
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
//计算数组总长度
int m = nums1.length;
int n = nums2.length;
int sum = m + n;
//中位数为第mid1小的数和第mid2小的数(下标+1)
int mid1 = sum / 2;
int mid2 = sum / 2 + 1;
if(sum % 2 == 1){ //判断中位数为两个数还是一个数
mid1++;
}
//递归二分
if(mid1 == mid2) return getMedian(nums1, 0, m - 1, nums2, 0, n - 1, mid1) / 1.0;
return (getMedian(nums1, 0, m - 1, nums2, 0, n - 1, mid1) + getMedian(nums1, 0, m - 1, nums2, 0, n - 1, mid2)) / 2.0;
}
//递归进行二分,输入两个数组及其左右区间(左闭右闭)和目标值(第mid小)
private int getMedian(int[] nums1, int left1, int right1, int[] nums2, int left2, int right2, int mid){
//终止条件
int len1 = right1 - left1 + 1;
int len2 = right2 - left2 + 1;
//if(len1 > len2) return getMedian(nums2, left2, right2, nums1, left1, right2, mid);
if(len1 == 0) return nums2[left2 + mid - 1]; //其中一个数组空了
if(len2 == 0) return nums1[left1 + mid - 1];
if(mid == 1) return Math.min(nums1[left1], nums2[left2]); //返回较小值
//两边数组定位到mid/2 - 1的位置(下标)
//数组不够长则直接指向末尾
int i = left1 + Math.min(mid / 2, len1) - 1;
int j = left2 + Math.min(mid / 2, len2) - 1;
//比较两个数组对应位置上值的大小,比较小的数组该位置左边可以舍弃
if(nums1[i] > nums2[j]){
return getMedian(nums1, left1, right1, nums2, j + 1, right2, mid - (j - left2 + 1));
}else{
return getMedian(nums1, i + 1, right1, nums2, left2, right2, mid - (i - left1 + 1));
}
}
}
dp[i] [j]
表示下标i和下标j之间(包括i,j
)是否为回文子串,是为true
,否为false
。从后向前遍历字符串,从前向后判断是否回文子串,若是则判断是否更新最大长度
class Solution {
public String longestPalindrome(String s) {
int leng = s.length();
if(leng == 1) return s;
//动态规划DP
//定义dp数组dp[i] [j]表示下标i和下标j之间(包括i,j)是否为回文子串
//是为true,否为false
boolean[][] dp = new boolean[leng][leng];
int left = 0; //存储最长回文子串的下标范围
int right = 0;
//从后向前遍历字符串,从前向后判断是否回文子串,若是则判断是否更新最大长度
for(int i = leng - 1; i >= 0; i--){
for(int j = i; j < leng; j++){
if(s.charAt(i) == s.charAt(j)){
if(j - i <= 1){
dp[i][j] = true; //长度小于1则直接为true("a""aa")
}else{
dp[i][j] = dp[i + 1][j - 1]; //否则判断中间是否回文子串
}
}
if(dp[i][j] == true && right - left < j - i){
left = i;
right = j;
}
}
}
return s.substring(left, right + 1);
}
}
中心扩散法,双指针,以一个元素或两个元素为中心向两边扩散直到字符串边界,求得该回文中心的最长回文子串长度,最后得到总体的最长回文子串长度
同样可计算回文子串个数
时间复杂度O(n ^ 2),空间复杂度O(1)
class Solution {
int left;
int right;
public String longestPalindrome(String s) {
int leng = s.length();
if(leng == 1) return s;
//中心扩散法
left = 0;
right = 0;
//以一个元素或两个元素为中心向两边扩散直到字符串边界
for(int i = 0; i < s.length(); i++){
centerSpread(s, i, i);
centerSpread(s, i, i + 1);
}
return s.substring(left, right + 1);
}
private void centerSpread(String s, int i, int j){
//双指针
//求得该回文中心的最长回文子串长度,最后得到总体的最长回文子串长度
while(i >=0 && j < s.length() && s.charAt(i) == s.charAt(j)){
if(right - left < j - i){
right = j;
left = i;
}
//向左右两边扩散
i--;
j++;
}
}
}
dp[i] [j]
表示下标i - 1
(包括i - 1
)之前的字符串s子串和下标j - 1
(包括j - 1
)之前的字符串p子串是否匹配
class Solution {
public boolean isMatch(String s, String p) {
//动态规划
int leng1 = s.length();
int leng2 = p.length();
//定义dp数组dp[i] [j]表示下标i - 1(包括i - 1)之前的字符串s子串
//和下标j - 1(包括j - 1)之前的字符串p子串是否匹配
boolean[][] dp = new boolean[leng1 + 1][leng2 + 1];
//初始化,dp[i][0] = false但dp[0][j]可能为true
dp[0][0] = true;
for(int i = 0; i <= leng1; i++){
for(int j = 1; j <= leng2; j++){
if(p.charAt(j - 1) == '*'){//当j-1指向*时,此时j一定大于等于2,'*'前一定有字符
//s[i]和p[j - 1]不匹配
dp[i][j] = dp[i][j - 2];
//s[i]和p[j - 1]不匹配
if(i > 0 && (p.charAt(j - 2) == s.charAt(i - 1) || p.charAt(j - 2) == '.')){
dp[i][j] = dp[i][j] || dp[i - 1][j];
}
}else{//当j-1指向字母或.时
//s[i]和p[j]匹配
if(i > 0 && (p.charAt(j - 1) == s.charAt(i - 1) || p.charAt(j - 1) == '.')){
dp[i][j] = dp[i - 1][j - 1];
}
}
}
}
return dp[leng1][leng2];
}
}
高*宽
)后移动较小的指针
class Solution {
public int maxArea(int[] height) {
//双指针,左右指针从数组两边出发向中间移动
int left = 0;
int right = height.length - 1;
int max = 0;
while(left < right){
//每次计算水量后移动较小的指针
int h = Math.min(height[right], height[left]);
int w = right - left;
int sum = h * w;
max = Math.max(max, sum);
if(h == height[right]){
right--;
}else{
left++;
}
}
return max;
}
}
i
遍历数组,左指针指向i+1
,右指针指向数组最右端,如果三数和大于0
则右指针向左移动,小于0
则左指针向右移动,等于0
则存储三个值(求和前去重),直到左右指针相遇i向前移动。
i,left,right
和上一个i,left,right
是否相同,相同的话得到的三数一致,跳过该数,可以使用Arrays.asList()
直接新建List
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
//双指针,数组中可能有重复元素,哈希表去重比较麻烦
List<List<Integer>> result = new ArrayList<>();
int leng = nums.length;
if(leng < 3) return result;
//首先将数组升序排序,使用i遍历数组,左指针指向i+1,右指针指向数组最右端
Arrays.sort(nums);
int left = 0;
int right = leng - 1;
for(int i = 0; i < leng; i++){
//如果i指向的数大于0可以直接返回结果
if(nums[i] > 0) break;
//在求和前判断当前i和上一个i是否相同,相同的话得到的三数一致,跳过该数
if(i > 0 && nums[i] == nums[i - 1]) continue;
left = i + 1;
right = leng - 1;
//直到左右指针相遇i向前移动
while(left < right){
if(nums[i] + nums[left] + nums[right] > 0){//如果三数和大于0则右指针向左移动
right--;
//判断当前right和上一个right是否相同,相同的话得到的三数一致,跳过该数
while(left < right && nums[right] == nums[right + 1]){
right--;
}
}else if(nums[i] + nums[left] + nums[right] < 0){//小于0则左指针向右移动
left++;
//判断当前left和上一个left是否相同,相同的话得到的三数一致,跳过该数
while(left < right && nums[left] == nums[left - 1]){
left++;
}
}else{//等于0则存储三个值(求和前去重)
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
//去重,注意这里左右指针判断方向不同
while(left < right && nums[right] == nums[right - 1]){
right--;
}
while(left < right && nums[left] == nums[left + 1]){
left++;
}
//下一轮
left++;
right--;
}
}
}
return result;
}
}
class Solution {
List<String> result = new ArrayList<>();
StringBuilder sb = new StringBuilder();
public List<String> letterCombinations(String digits) {
//回溯
if(digits.length() == 0) return result;
//定义数字-字母对应数组(0——9)
String[] num = new String[]{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
backtracking(digits, 0, num);
return result;
}
//回溯函数,输入字符串,解析位置,数字-字母对应数组
private void backtracking(String digits, int start, String[] num){
if(start >= digits.length()){
result.add(sb.toString());
return;
}
String s = num[digits.charAt(start) - '0']; //当前位对应的字符串
for(int i = 0; i < s.length(); i++){ //遍历当前位对应字符串
sb.append(s.charAt(i));
backtracking(digits, start + 1, num);
sb.deleteCharAt(sb.length() - 1); //回溯
}
}
}
n
次,然后左右指针同时移动直到右指针到链表尾部,此时左指针指向倒数第n
个节点。同时定义左指针前一个节点用于删除操作,也可以直接定位到倒数第n-1
个节点直接删除下一个节点。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//双指针
ListNode virtualHead = new ListNode(0, head); //虚拟头节点
ListNode pre = virtualHead; //要删除的前一个节点
ListNode left = virtualHead.next; //要删除的节点
ListNode right = virtualHead.next; //指向null时左指针为倒数第n个节点
while(n > 0){ //右指针先移动n次
right = right.next;
n--;
}
while(right != null){//左右指针同时移动
pre = left;
left = left.next;
right = right.next;
}
//删除left指向节点
pre.next = pre.next.next;
return virtualHead.next;
}
}