思路与算法

可以使用两个指针分别指向位置 0和 n-1,每次比较两个指针对应的数,选择较大的那个逆序放入答案并移动指针,这种方法无需处理某一指针移动至边界的情况。

代码

class Solution {
    public int[] sortedSquares(int[] nums) {
        int n = nums.length;
        int[] ans = new int[n];
        for(int i = 0,j = n - 1,pos = n - 1;i <= j;){
            if(nums[i]*nums[i] > nums[j]*nums[j]){
                ans[pos] = nums[i]*nums[i];
                i++;
            }else{
                ans[pos] = nums[j]*nums[j];
                j--;
            }
            pos--;
        }
        return ans;

    }
}

复杂度分析

时间复杂度:O(n)O(n),其中 nn 是数组 \textit{nums}nums 的长度。

空间复杂度:O(1)O(1)。除了存储答案的数组以外,我们只需要维护常量空间。

例题1:

题目:

给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 1:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]

方法一(自己的菜鸟代码):

class Solution {
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        int[] ans = new int[n];
        if(k > n){
            k = k%n;
        }
        for(int i = 0,j = n - 1,pos = n - k,flag = 0;i <= j;){
            if(pos <= n - 1){
                ans[i] = nums[pos];
                pos++;
            }else{
                ans[i] = nums[flag];
                flag++;
            }
            i++;
        }
        for(int i = 0;i <= n - 1;i++){
            nums[i] = ans[i];
        }

    }
}

方法二  :数组翻转

                                                                   操作                                   结果

原始数组

1~2~3~4~5~6~7

1 2 3 4 5 6 7

翻转所有元素 

 7~6~5~4~3~2~1

7 6 5 4 3 2 1

翻转 [0, k\bmod n - 1][0,kmodn−1] 区间的元素

5~6~7~4~3~2~1

5 6 7 4 3 2 1

翻转 [k\bmod n, n - 1][kmodn,n−1] 区间的元素

 5~6~7~1~2~3~4

5 6 7 1 2 3 4

该方法基于如下的事实:当我们将数组的元素向右移动 kk 次后,尾部 k\bmod nkmodn 个元素会移动至数组头部,其余元素向后移动 k\bmod nkmodn 个位置。

该方法为数组的翻转:我们可以先将所有元素翻转,这样尾部的 k\bmod nkmodn 个元素就被移至数组头部,然后我们再翻转 [0, k\bmod n-1][0,kmodn−1] 区间的元素和 [k\bmod n, n-1][kmodn,n−1] 区间的元素即能得到最后的答案。

class Solution {
    public void rotate(int[] nums, int 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 start, int end) {
        while (start < end) {
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start += 1;
            end -= 1;
        }
    }
}

复杂度分析

时间复杂度:O(n)O(n),其中 nn 为数组的长度。每个元素被翻转两次,一共 nn 个元素,因此总时间复杂度为 O(2n)=O(n)O(2n)=O(n)。

空间复杂度:O(1)O(1)。

例题2:

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

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:

必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

思路及解法:

使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。

右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。

注意到以下性质:

左指针左边均为非零数;

右指针左边直到左指针处均为零。

因此每次交换,都是将左指针的零与右指针的非零数交换,且非零数的相对顺序并未改变。

代码:

class Solution {
    public void moveZeroes(int[] nums) {
            int left = 0,right = 0;
            int n = nums.length;
            while(right < n){
                if(nums[right] != 0){
                    swap(nums,left,right);
                    left++;
                }
                right++;
            }
    }

    public void swap(int[] nums,int left,int right){
            int temp = nums[left];
            nums[left] = nums[right];
            nums[right] = temp;
    }
}

例题3:

给定一个已按照 非递减顺序排列  的整数数组 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]
示例 3:

输入:numbers = [-1,0], target = -1
输出:[1,2]

代码:

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int low = 0;
        int high = numbers.length - 1;
        while(low < high){
            int sum = numbers[low] + numbers[high];
            if(sum == target){
                return new int[]{low+1,high+1};
            }else if(sum < target){
                low++;
            }else{
                high--;
            }
        }
        return new int[]{-1,-1};
    }
}

例题4:

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:

输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]

代码:

class Solution {
    public void reverseString(char[] s) {
            for(int left = 0,right = s.length - 1;left < right;left++,right--){
                char temp = s[left];
                s[left] = s[right];
                s[right] = temp;
            }
    }
}

例题5:

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

示例:

输入:"Let's take LeetCode contest"
输出:"s'teL ekat edoCteeL tsetnoc"

代码:

class Solution {
    public String reverseWords(String s) {
            StringBuffer str = new StringBuffer();
            int end = s.length();
            int length = 0;
            while(length < end){
                int start = length;//记录每个单词的首位索引
                while(length < end && s.charAt(length) != ' '){
                    length++;//记录每个单词的长度
                }
                for(int j = start;j < length;j++){
                    str.append(s.charAt(length + start - j - 1));//字符串的拼接
                }
                while(length < end && s.charAt(length) == ' '){
                    length += 1;
                    str.append(' ');
                }
            }
            return str.toString();
    }
}

例题6:

给定一个头结点为 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,我们返回第二个结点。

思路:

快慢指针法

我们可以继续优化方法二,用两个指针 slow 与 fast 一起遍历链表。slow 一次走一步,fast 一次走两步。那么当 fast 到达链表的末尾时,slow 必然位于中间。

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode middleNode(ListNode head) {
            ListNode slow = head,fast = head;
            while(fast != null && fast.next != null){
                    slow = slow.next;
                    fast = fast.next.next;
            }
            return slow;
    }
}

例题7:

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

示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:

输入:head = [1], n = 1
输出:[]
示例 3:

输入:head = [1,2], n = 1
输出:[1]

思路:

我们也可以在不预处理出链表的长度,以及使用常数空间的前提下解决本题。

由于我们需要找到倒数第 n个节点,因此我们可以使用两个指针 first 和 second 同时对链表进行遍历,并且 first 比second 超前 n 个节点。当 first 遍历到链表的末尾时,second 就恰好处于倒数第 n 个节点。

具体地,初始时 first 和second 均指向头节点。我们首先使用 first 对链表进行遍历,遍历的次数为 n。此时,first 和 second 之间间隔了n−1 个节点,即 first 比 second 超前了 n 个节点。

在这之后,我们同时使用first 和 second 对链表进行遍历。当first 遍历到链表的末尾(即 first 为空指针)时,second 恰好指向倒数第 n 个节点。

根据方法一和方法二,如果我们能够得到的是倒数第 n 个节点的前驱节点而不是倒数第 n 个节点的话,删除操作会更加方便。因此我们可以考虑在初始时将second 指向哑节点,其余的操作步骤不变。这样一来,当 first 遍历到链表的末尾时,second 的下一个节点就是我们需要删除的节点。

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
            ListNode d = new ListNode(0,head);
            ListNode fast = head;
            ListNode slow = d;
            for(int i = 0;i < n;i++){
                fast = fast.next;
            }
            while(fast != null){
                fast = fast.next;
                slow = slow.next;
            }
            slow.next = slow.next.next;
            ListNode ans = d.next;
            return ans;
    }
}