leetcode(2) 9.24 - 9.30

本文探讨如何在非递减排序的整数数组中计算每个元素的平方并保持排序,介绍两种方法:一种是利用Arrays.sort()进行排序,另一种是使用双指针技巧。讨论了它们的时间复杂度和空间复杂度,并涉及了其他类似问题如链表操作和字符串反转的解决方案.
摘要由CSDN通过智能技术生成

977. 有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

方法1 : 直接排序 - 将数组nums中的数平方后直接输出。

知识点:Java的Arrays类中的sort()方法,该方法是Arrays类的静态方法,在需要对数组进行排序时使用。

class Solution {
    public int[] sortedSquares(int[] nums) { 
        int[] a = new int[nums.length];
        for(int i = 0 ; i < nums.length ; i++ ){
            a[i] = nums[i] * nums[i];
        }
        Arrays.sort(a);
        return a;
    }
}
  • 时间复杂度:O(nlogn),其中n是数组nums的长度。
  • 空间复杂度:O(long n)。除了存储答案的数组以外,需要O(log n)的栈空间进行排序。
方法2: 双指针
class Solution {
    public int[] sortedSquares(int[] nums) {
        int[] a = new int[nums.length];
        int l = 0 , r = nums.length - 1 ;//左右指针
        int x = nums.length-1 ;
        while( l <= r){ //循环判断条件
         	//逆序插入  如果右边数值较大 则将较大数值插入 右指针向左移动
            if( nums[l] * nums[l] <= nums[r] * nums[r]){ 
                a[x] = nums[r] * nums[r]; 
                r--;
            }else{
                a[x] = nums[l] * nums[l] ;
                l++;
            }
            x--; //对应的x插入以后 x的位置也将移动
        }
        return a;
    }
}

189. 旋转数组

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

进阶:

  • 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
  • 你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?

示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]

方法一:使用额外空间
class Solution {
  public void rotate(int[] nums, int k) {
      int[] a = nums.clone();; 
      for( int i = 0 ; i < nums.length ; i++){  
          nums[(i+k) % nums.length] = a[i]  ;  
      } 
  }
} 

注意点:

  • 新数组必须复制原数组,不能new一个新数组。
  • 数字下标溢出问题: (i+k)%nums.length 对数组长度取余
  • 给数组赋值:必须是从i开始,给旋转后的数组下标赋值。
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)。
方法二:数组反转 !!!
class Solution {
    public void rotate(int[] nums, int k) {
        k = k % nums.length;
        reverse(nums,0,nums.length-1);
        reverse(nums,0,k-1);
        reverse(nums,k,nums.length-1);
    }
    public void reverse(int[] nums, int s,int e) {
        while( s < e ){
            int a = nums[s];
            nums[s] = nums[e];
            nums[e] = a;
            s++;
            e--;
        }
    }
}

注意:

  • 该实现方法中的reverse方法要自己实现。
  • 实现原理动图
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)。
方法三:环状替换 (重要!)
class Solution {
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        k = k % n;
        int count = gcd(k, n);
        for (int start = 0; start < count; ++start) {
            int current = start;
            int prev = nums[start];
            do {
                int next = (current + k) % n;
                int temp = nums[next];
                nums[next] = prev;
                prev = temp;
                current = next;
            } while (start != current);
        }
    }

    public int gcd(int x, int y) {
        return y > 0 ? gcd(y, x % y) : x;
    }
}
 

283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

方法:使用双指针

思路:设置两个指针, i 指针遍历数组,当nums[i]不为零时,赋值给 j 指针所指的位置。然后,i 指针遍历结束以后将 j 指针后续的所有位置置零即可。

class Solution {
    public void moveZeroes(int[] nums) {
        int l = 0;
        for(int i = 0 ; i < nums.length ;i++){
            if(nums[i]!=0){
                nums[l++] = nums[i];
            }
        }
        for(int j = l ; j<nums.length ;j++){
            nums[j] = 0;
        }
    }
}

167. 两数之和 II - 输入有序数组

给定一个已按照 非递减顺序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
示例 2:
输入:numbers = [2,3,4], target = 6
输出:[1,3]

方法:双指针
class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int l=0 , r=numbers.length-1;
        while(l<=r){
            if(numbers[l]+numbers[r]>target)
                r--;
            else if(numbers[l]+numbers[r]<target)
                l++;
            else if(numbers[l]+numbers[r]==target)
                return new int[]{l + 1, r + 1};
        } 
        return new int[]{-1, -1};
    }
}
归纳总结:
  1. 双指针:其需要充分利用数组有序这一特征,在某些情况下简化一些运算。
    常见应用:两数之和,判断链表是否有环,奇偶排序,求单链表的中间元素,给出n个数问最多能组成多少个三角形。
  2. 二分查找:使用二分查找的重要条件需要有序顺序表

541. 反转字符串II

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

  • 如果剩余字符少于 k 个,则将剩余字符全部反转。
  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

示例 1:
输入:s = “abcdefg”, k = 2
输出:“bacdfeg”
示例 2:
输入:s = “abcd”, k = 2
输出:“bacd”

方法:双指针法
步骤:
  • 1 首先将字符串转化为字符数组。
  • 2 处理好左指针,它从0位开始,每隔2k个位跳转,可放在for循环中声明。
  • 3 处理右指针
    · 首先,右指针应该在左指针的基础上往后移动k位。( l - r = k )需要有k位的位差
    · 如果当最后没有k位时,后续的字符也需要反转,因此右指针的取值为 Math.min(ch.length-1,l+k-1)
class Solution {
    public String reverseStr(String s, int k) {
        char[] sc = s.toCharArray();
        for(int l=0 ; l<s.length() ; l = l+2*k){
            int r = l+k-1;
            reverse(sc,l,Math.min(r,s.length()-1));
        }
        return String.valueOf(sc);
    }

    void reverse (char[] a ,int l ,int r){
        while(l<r){
            char b;
            b = a[l];
            a[l]=a[r];
            a[r]=b;
            l++;
            r--;
        }
    }
}

557. 反转字符串III

给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

示例:
输入:“Let’s take LeetCode contest”
输出:“s’teL ekat edoCteeL tsetnoc”

方法:双指针
步骤:
  • 1 将字符串转化为字符数组。
  • 2 使用for循环判断空格的位置,left从0开始,right为i-1的位置。
  • 3 判断有空格时进行反转。
  • 4 注意! 需要对最后一个单词单独进行反转,因为最后一个单词后没有空格
class Solution {
    public String reverseWords(String s) {
        char[] sc = s.toCharArray();
        int l = 0 ,count=0;
        for(int i = 0 ; i <sc.length ; i++){
            if(sc[i]==' '){ 
                reverse(sc,l,i-1);
                l= i + 1;
                count++;
            }
        }
        if(count==0){
            reverse(sc,0,sc.length-1);
        }else{
            reverse(sc,l,sc.length-1);
        }
        return String.valueOf(sc);
    }
    void reverse(char[] s,int l ,int r){
        while(l<r){
            char a=s[l]; 
            s[l]=s[r];
            s[r]=a;
            l++;
            r--;
        }
    }
}

876. 链表的中间结点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。

示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。

方法1 :使用数组实现

对链表进行遍历的同时将遍历到的元素一次放入数组中,然后取对应的中间节点即可。

class Solution {
    public ListNode middleNode(ListNode head) {
        //数组
        ListNode[] a = new ListNode[100];
        int count = 0 ;
        while( head != null) {
            a[count++]=head;
            head = head.next;
        }
        return a[count/2];
    }
}
  • 时间复杂度:O(N),其中 N 是给定链表中的结点数目。
  • 空间复杂度:O(N),即数组a用去的空间。
方法2:单指针

使用单指针解题,首先使用单指针对链表进行进行遍历,记录结点数。然后第二次遍历找到中间结点,然后返回。

class Solution {
    public ListNode middleNode(ListNode head) {
        //单指针 
        int count = 0 ; //用来记录链表的结点数
        ListNode a = head ;
        while(a != null){//第一次遍历 确定节点个数
            ++count;
            a = a.next;
        }
        int k = 0 ;
        a = head ;
        while( k < count/2){//第二次遍历 找到中间节点。
            ++k;
            a = a.next;
        }
        return a;
    }
}
  • 时间复杂度:O(N),其中N是给定链表的结点数目。
  • 空间复杂度:O(1),只需要常数空间存放变量和指针。
方法3:快慢指针

使用两个指针来遍历链表,快指针一次走两步,慢指针一次走一步,当快指针到达链表末端时,慢指针刚好到达队中。

class Solution {
    public ListNode middleNode(ListNode head) {
        //快慢指针
        ListNode f = head;
        ListNode s = head;
        while( f != null && f.next != null ){
            s = s.next;
            f = f.next.next;
        }
        return s;
    }
}
  • 时间复杂度:O(N),其中N是给定链表的结点数目。
  • 空间复杂度:O(1),只需要常数空间存放slow和fast两个指针。

19. 删除链表的倒数第n个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
19

方法:双指针扫描
整体思路:让快指针先移动n步,然后前后指针共同移动直至快指针到达尾部。
	- 设置预先指针pre,为了防止只有一个结点且要被删除的情况。 
	- 设预先指针 pre 的下一个节点指向 head,设前指针为 start,后指针为 end,二者都等于 pre
	- start 先向前移动n步
	- 之后 start 和 end 共同向前移动,此时二者的距离为 n,当 start 到尾部时,end 的位置恰好为倒数第 n 个节点
	- 因为要删除该节点,所以要移动到该节点的前一个才能删除,所以循环结束条件为 start.next != null
	- 删除后返回 pre.next,为什么不直接返回 head 呢,因为 head 有可能是被删掉的点
	- 时间复杂度:O(n) 
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode pre = new ListNode(0);//初始化一个节点值为0的空结点
        pre.next = head;
        ListNode f = pre , s = pre ;
        for(int i=n ; i>-1 ; i--){
            f = f.next;
        }
        while(f != null){
            s = s.next;
            f = f.next;
        }
        s.next = s.next.next;
        return pre.next;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值