手撕力扣之双指针【链表数组】:删除链表的倒数第N个节点、链表中倒数第k个节点、分隔链表、环形链表I、II、寻找重复数、颜色分类、最短无序连续子数组、和为s的连续正数序列、翻转字符串里的单词、验证回文串

力扣019. 删除链表的倒数第N个节点

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        while(n-- && fast != NULL) {
            fast = fast->next;
        }
        fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
        while (fast != NULL) {
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return dummyHead->next;
    }
};

作者:carlsun-2
链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/solution/19-shan-chu-lian-biao-de-dao-shu-di-nge-jie-dia-85/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

剑指 Offer 22. 链表中倒数第k个节点

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(k--) {
            fast = fast->next;
        }
        while(fast) {
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};

牛客NC69 链表中倒数第K个节点
输入一个链表,输出该链表中倒数第k个结点。
思路:
设置两个指针 fast 、slow,fast先走k步,如果走不到第k步(nullptr节点是可以走到的,但是nullptr节点没有next,所以只能走到nullptr),说明链表长度不够k,直接返回nullptr;然后,令 fast 和 slow 开始同步往下移动,直到 fast 移动到nullptr,此时slow就是倒数第 k 个节点的,返回即可。

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(pListHead == nullptr || k < 1){
            return nullptr;
        }
        ListNode *pre = pListHead;
        ListNode *last = pListHead;
        while(k > 0){
            if(last == nullptr){
                return nullptr;
            }
            last = last->next;
            k--;
        }
        while(last != nullptr){
            last = last->next;
            pre = pre->next;
        }
        return pre;
    }
};

力扣086. 分隔链表
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        if(!head) return nullptr;

        ListNode* smaller_head = new ListNode(985); // 定义一个新的链表,用来存比x小的值
        ListNode* smaller = smaller_head; // 定义一个指针,初始位置指在上面的新链表的头结点
        ListNode* larger_head = new ListNode(211); // 定义一个新的链表,用来存比x大或等于x的值
        ListNode* larger = larger_head; // 定义一个指针,初始位置指在上面的新链表的头结点

        while(head)
        {
            if(head -> val < x)
            {
                smaller -> next = head;
                smaller = smaller -> next;
            }
            else
            {
                larger -> next = head;
                larger = larger -> next;
            }
            head = head -> next;
        }

        larger -> next = nullptr;
        smaller -> next = larger_head -> next; // 由此可得,larger_head的头结点,其实是一个dummy值(哑值)

        return smaller_head -> next; // 由此可得,smaller_head的头结点,其实是一个dummy值(哑值)
    }
};

作者:superkakayong
链接:https://leetcode-cn.com/problems/partition-list/solution/zi-jie-ti-ku-86-zhong-deng-fen-ge-lian-biao-partit/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

力扣141.环形链表
在这里插入图片描述fast和slow各自再走一步, fast和slow就相遇了
这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            // 快慢指针相遇,说明有环
            if (slow == fast) return true;
        }
        return false;
    }
};


作者:carlsun-2
链接:https://leetcode-cn.com/problems/linked-list-cycle/solution/141-huan-xing-lian-biao-shuang-zhi-zhen-zhao-huan-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

力扣142. 环形链表 II
主要考察两知识点:
1.判断链表是否环
2.如果有环,如何找到这个环的入口
在这里插入图片描述
那么相遇时:
slow指针走过的节点数为: x + y
fast指针走过的节点数: x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为我们要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以我们要求x ,将x单独放在左面:x = n (y + z) - y
在从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。当 n为1的时候,公式就化解为 x = z.
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点.

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            // 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
            if (slow == fast) {
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index2; // 返回环的入口
            }
        }
        return NULL;
    }
};


作者:carlsun-2
链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/142-huan-xing-lian-biao-ii-jian-hua-gong-shi-jia-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

力扣287. 寻找重复数
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
在这里插入图片描述
思路同上一题:
如上图,slow和fast会在环中相遇,先假设一些量:起点到环的入口长度为m,环的周长为c,在fast和slow相遇时slow走了n步。则fast走了2n步,fast比slow多走了n步,而这n步全用在了在环里循环(n%c==0)。
当fast和last相遇之后,我们设置第三个指针finder,它从起点开始和slow(在fast和slow相遇处)同步前进,当finder和slow相遇时,就是在环的入口处相遇,也就是重复的那个数字相遇。

为什么 finder 和 slow 相遇在入口
fast 和 slow 相遇时,slow 在环中行进的距离是n-m,其中 n%c== 0。这时我们再让 slow 前进 m 步——也就是在环中走了 n 步了。而 n%c==0 即 slow 在环里面走的距离是环的周长的整数倍,就回到了环的入口了,而入口就是重复的数字。
我们不知道起点到入口的长度m,所以弄个 finder 和 slow 一起走,他们必定会在入口处相遇。

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        
        int fast = 0, slow = 0;
        while(true){
            fast = nums[nums[fast]];
            slow = nums[slow];
            if(fast == slow)
                break;
        }
        int finder = 0;
        while(true){
            finder = nums[finder];
            slow = nums[slow];
            if(slow == finder)
                break;        
        }
        return slow;
    }
};

作者:zjczxz
链接:https://leetcode-cn.com/problems/find-the-duplicate-number/solution/kuai-man-zhi-zhen-de-jie-shi-cong-damien_undoxie-d/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

力扣075. 颜色分类
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
思路:
在这里插入图片描述
注意:为什么当nums[i]==0的时候为什么不需要循环判断,因为这个时候nums[p0]交换过去的值肯定是1,因为开始的时候i和p0是一起走的,只有nums[i]==1时,才会跳过去,而这时候p2指向的是第一个1.

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int n = nums.size();
        int p0 = 0, p2 = n - 1;
        for (int i = 0; i <= p2; ++i) {
            while (i <= p2 && nums[i] == 2) {
                swap(nums[i], nums[p2]);
                --p2;
            }
            if (nums[i] == 0) {
                swap(nums[i], nums[p0]);
                ++p0;
            }
        }
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/sort-colors/solution/yan-se-fen-lei-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

力扣581. 最短无序连续子数组
给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
你找到的子数组应是最短的,请输出它的长度。
思路:
很简单,如果最右端的一部分已经排好序,这部分的每个数都比它左边的最大值要大,同理,如果最左端的一部分排好序,这每个数都比它右边的最小值小。所以我们从左往右遍历,如果i位置上的数比它左边部分最大值小,则这个数肯定要排序, 就这样找到右端不用排序的部分,同理找到左端不用排序的部分,它们之间就是需要排序的部分

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        int left=0;int right=nums.size();//这里right为i不可能取到的值,这样是防止那种递增数组
        int maxtemp=INT_MIN;  int mintemp=INT_MAX;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]<maxtemp)  right=i;
            maxtemp=max(maxtemp,nums[i]);
        }
        for(int i=nums.size()-1;i>=0;i--)
        {
            if(nums[i]>mintemp)  left=i;
            mintemp=min(mintemp,nums[i]);
        }
        return right-left==nums.size()? 0:right-left+1;
    }
};

力扣剑指 Offer 57 - II. 和为s的连续正数序列
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
思路:
滑动窗口的重要性质是:窗口的左边界和右边界永远只能向右移动,而不能向左移动。
对于这道题来说,数组就是正整数序列 [1, 2, 3, …, n]。我们设滑动窗口的左边界为 i,右边界为 j,则滑动窗口框起来的是一个左闭右开区间 [i, j)。注意,为了编程的方便,滑动窗口一般表示成一个左闭右开区间。在一开始i=1,j=1,滑动窗口位于序列的最左侧,窗口大小为零。

vector<vector<int>> findContinuousSequence(int target) {
    int i = 1; // 滑动窗口的左边界
    int j = 1; // 滑动窗口的右边界
    int sum = 0; // 滑动窗口中数字的和
    vector<vector<int>> res;

    while (i <= target / 2) {
        if (sum < target) {
            // 右边界向右移动
            sum += j;
            j++;
        } else if (sum > target) {
            // 左边界向右移动
            sum -= i;
            i++;
        } else {
            // 记录结果
            vector<int> arr;
            for (int k = i; k < j; k++) {
                arr.push_back(k);
            }
            res.push_back(arr);
            // 左边界向右移动
            sum -= i;
            i++;
        }
    }

    return res;
}

作者:nettee
链接:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/solution/shi-yao-shi-hua-dong-chuang-kou-yi-ji-ru-he-yong-h/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

力扣151. 翻转字符串里的单词
给定一个字符串,逐个翻转字符串中的每个单词。
说明:
无空格字符构成一个 单词 。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
思路:双指针+栈

class Solution {
public:
    string reverseWords(string s) {
        if(s.size()<2)return s;
        stack<string> st;
        int size=s.size(),i=0;
        string cur="";
        while(i<size){
            if(s[i]!=' '){
                int j=i;
                while(s[j]!=' '&&j<size){
                    j++;
                }
                st.push(s.substr(i,j-i));
                i=j;
            }
            i++;
        }`
        //依次弹出单词,然后合并,注意,该合并完成的字符串尾部有个空格。
        while(!st.empty()){
            cur+=st.top()+" ";
            st.pop();
        }
        //删去空格
        cur.pop_back();
        return cur;
        }
};

力扣125. 验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
思路:
大写的A 是65,小写的a 是97,
字母依次增大,号码也相应增大,
相互之间需要转换的话,加或减去他们的差值就可以了,
也就是大写变小写的话,就+32

记一笔 c++的几个内置函数
islower(char c) 是否为小写字母
isupper(char c) 是否为大写字母
isdigit(char c) 是否为数字
isalpha(char c) 是否为字母
isalnum(char c) 是否为字母或者数字
toupper(char c) 字母小转大
tolower(char c) 字母大转小

class Solution {
public:
    bool isPalindrome(string s) {
        string tmp;
        for (auto c : s) {
            if (islower(c) || isdigit(c))  tmp += c;
            else if (isupper(c)) tmp += (c + 32);
        }
        int i = 0, j = tmp.size() - 1;
        while (i < j) {
            if (tmp[i] != tmp[j]) return false;
            i++;
            j--;
        }
        return true;
    }
};

作者:karlzhang
链接:https://leetcode-cn.com/problems/valid-palindrome/solution/9877-ci-ti-mu-de-shou-xi-7ge-zi-mu-shu-zi-pan-duan/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值