LeetCode[70/83/88]爬楼梯、 删除排序链表中的重复元素、合并两个有序数组 C/C++——第六天

70.爬楼梯

题目描述[简单]:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例1:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。

  1. 1 阶 + 1 阶 + 1 阶
  2. 1 阶 + 2 阶
  3. 2 阶 + 1 阶

思路[动态规划]:

其实他跟斐波拉契序列有点相似,只是F[2]=2。
这是一个典型的动态规划问题。

动态规划法的求解过程的三个阶段组成:

  1. 划分子问题;将原问题分解为若干个相互重叠的子问题
  2. 确定动态规划函数;分析问题是否满足最优性原理,找出动态规划函数的递推式;
  3. 填写表格。设计表格,利用递推式自底向上计算各子问题的解,并填表,实现动态规划过程。

一、划分子问题

我们划分子问题数量=爬第n阶楼梯的方法数量=2 部分之和

  1. 爬上 n-1 阶楼梯的方法数量,因为再爬1阶就能到第n阶
  2. 爬上 n-2阶楼梯的方法数量,因为再爬2阶就能到第n阶

二、确定动态规划函数
我们不难发现,爬n阶楼梯最终都转变成爬n-1阶楼梯的方法数量和爬n-1阶楼梯的方法数量。
因此可得出一下动态规划函数:
dp[ i ]=dp[ i - 1 ] + dp[ i - 2 ] (当且仅当 i>2是该方程成立)

确定初始值:
dp[1] = 1 ; dp[2] = 2 ;

但是如果只是问走1阶或2阶,我们直接返回原来值就可以了,动态规划函数是从3开始的,即从3开始进入循环。但是在数组里面,我们还是要设dp[1]、dp[2]的值。

C++代码:

class Solution {
    
    
public:
    int climbStairs(int n) {
    
    
        if(n<=2)
        return n;
        int* dp = new int[n+1] ; //使用 new 来创建动态数组
        dp[1] = 1 ;
        dp[2] = 2 ;
        for(int i = 3 ; i <= n ; i++){
    
    
            dp[i] = dp[i - 1] + dp[i - 2] ;
        }
        return dp[n] ;
    }
};

本题用C++巧妙地一点是可以利用new来创建动态数组。

时间复杂度:O(n) ;

83.删除排序链表中的重复元素

题目描述[简单]:
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。

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

思路[链表指针特性]:

本题因为链表默认排好序了,跟之前做过删除数组中重复元素的比较类似。我们先定义一个指针h使他等于头指针head。遍历整个链表,当h指向的元素与h指向的下一个元素相等时,我们就删除下一个元素。我们利用链表的特性,通过将h指向的next直接指向next的next,从而达到删除相同元素的目的。删除完之后,再让h等于h指向的下一个结点,即开始顺着链表向后遍历。当我们遍历到下一个next指向为NULL时,说明到达链表结尾,就结束遍历。

本题有说明data为val,所以我们指针指向的不是data是val。

C++代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */Oclass Solution {
    
    
public:
    ListNode* deleteDuplicates(ListNode* head) {
    
    
        ListNode* h = head ;
        while(h != NULL && h->next != NULL ){
    
    
        if(h->val == h->next->val )
            h->next = h->next->next;
        else h = h ->next;
        }
        return head;
    }
};

创建一个新的指针使之等于头指针,而不直接使用头指针原因是:最后要返回头结点,得先保存下来,后面的循环会改变这个结点的位置,所以创造一个新的是最好的。

最后返回head: 虽然head没变,但head的next变了,所以整个链表就变了。
而没有返回h:h在这里可理解为工作结点:用于遍历链表,以及更改链表结构。 head才代表着这个链表,因其始终指向链表的表头(不论你是否修改了链表)。

时间复杂度:O(n);

88.合并两个有序数组

题目描述[简单]:
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

示例1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

思路[逆向双指针]:

比较两个数组,选择一个最大的塞到nums1的尾部。在nums2都塞到nums1之后,就不用再继续处理了。

C++代码:

class Solution {
    
    
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
    
    
        int i = nums1.size() ;

        while (n > 0) {
    
    
            if (m > 0 && nums1[m-1] > nums2[n-1]) {
    
    
                nums1[--i] = nums1[--m];  //替代swap
            }else{
    
    
                nums1[--i] = nums2[--n]; 
            }
          
        }
    }
};

猜你喜欢

转载自blog.csdn.net/Lailalalala/article/details/126070760