2020年2月

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/reverse-string

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

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

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

你可以假设数组中的所有字符都是ASCII码表中的可打印字符。

示例 1:

  • 输入:["h","e","l","l","o"]
  • 输出:["o","l","l","e","h"]

示例 2:

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

二、解析

从字符串开头遍历到中间位置,同时反转每个对应位置上的元素。很基础的题目,没有坑。

三、代码

class Solution {
public:
    void reverseString(vector<char>& s) {
        size_t i, len;
        char ch;

        len = s.size();
        for (i = 0; i < len / 2; i++) {
            ch = s[i];
            s[i] = s[len - 1 - i];
            s[len - 1 - i] = ch;
        }
    }
};

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/reverse-integer

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

示例:

  • 输入: 123
  • 输出: 321

示例 2:

  • 输入: -123
  • 输出: -321

示例 3:

  • 输入: 120
  • 输出: 21

二、题解

题目很简单,基本的思路就是对n循环取模,然后乘10累加。但是问题的关键点不是在这里,关键点是数据溢出。假设传入数据比INT_MAX小,但是反转后比INT_MAX大,那就会产生溢出。

INT_MAX = 2147483647, INT_MIN = -2147483648;

假设反转后的值是output,被反转数字模10后得到y,那么output应该被设置为output * 10 + y

此时output溢出的条件:

  1. output > INT_MAX / 10或者output = INT_MAX / 10 && y > 7
  2. output < INT_MIN / 10或者output = INT_MIN / 10 && y < -8

以第一点为例,假设此时output大于最大值除以10,那么它乘以10后肯定会大于最大值,也就会溢出。如果它等于最大值除10,那么只要y大于最大值取模,也会溢出。

三、代码

class Solution {
public:
    int reverse(int x) {
        int output = 0, y;
        while (x) {
            y = x % 10;
            if (output > INT_MAX / 10 || (output == INT_MAX / 10 && x > 7)) return 0;
            if (output < INT_MIN / 10 || (output == INT_MIN / 10 && x < -8)) return 0;
            output = output * 10 + y % 10;
            x /= 10;
        }
        return output;
    }
};

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/copy-list-with-random-pointer

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。要求返回这个链表的深拷贝。

我们用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个[val, random_index]表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。

示例 1:

  • 输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
  • 输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

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

提示:

  • -10000 <= Node.val <= 10000。
  • Node.random 为空(null)或指向链表中的节点。
  • 节点数目不超过 1000 。

二、题目解析

《剑指offer》原题,详细解析《剑指offer》面试题35:复杂链表的复制

三、代码

class Solution {
private:
    /*
     * 克隆所有的节点,并串到原始节点中去
     * @head 链表的头结点
     */
    void clone_all_nodes(Node *head) {
        Node *node, *clone;
        node = head;
        while (node) {
            // 拷贝节点
            clone = new Node(node->val);
            clone->next = node->next;
            clone->random = node->random;
            // 修改p节点的下一个节点指向新创建的节点
            node->next = clone;
            // 移动p指针到下一个节点
            node = clone->next;
        }
    }

    /*
     * 修正所有克隆节点的随机指针的地址
     * @head 链表的头结点
     */
    void fix_random_nodes(Node *head) {
        // node/q节点分别表示原始节点和被克隆的节点
        Node *node, *clone;
        node = head;
        while (node) {
            clone = node->next;
            // 修正q节点的random指针,注意random可能为null
            if (clone->random)
                clone->random = clone->random->next;
            node = clone->next;
        }
    }

    Node *reconnect_nodes(Node *head) {
        Node *node, *clone;
        Node *new_head = head->next;

        node = head;
        while (node) {
            clone = node->next;
            // 备份下一个节点的指针
            node->next = clone->next;
            // 连接到新克隆的节点,注意next可能为null
            if (clone->next) {
                clone->next = clone->next->next;
            }
            node = node->next;
        }
        return new_head;
    }

public:
    Node *copyRandomList(Node *head) {
        Node *new_head;
        if (head == nullptr) {
            return nullptr;
        }
        clone_all_nodes(head);
        fix_random_nodes(head);
        new_head = reconnect_nodes(head);
        return new_head;
    }
};

一、题目描述

请实现函数complex_list_node *clone_list(complex_list_node *head),复制一个复杂链表。在复杂链表中,每个节点除了有一个next指针指向下一个节点,还有一个random指针指向链表中的随机节点或nullptr。节点的C++定义如下:

struct complex_list_node {
    int val;
    struct complex_list_node *next;
    struct complex_list_node *random;
};

如下是一个复杂链表示例,其中虚线是random指针:

二、思路

复制过程可以分为三步,第一步,复制链表中的所有节点并插入到对应节点后,random指针和原节点指针保持一致:

第二步,修改所有复制节点的random指针,修改成被复制出来的节点:

第三步,把所有新复制出来的节点从原始链表中移除:

三、代码实现

链表节点定义:

// 链表节点定义
struct complex_list_node {
    int val;
    struct complex_list_node *next; // 下一个节点
    struct complex_list_node *random; // 随机节点
};

第一步克隆所有节点函数:

/*
 * 克隆所有的节点,并串到原始节点中去
 * @head 链表的头结点
 */
static void clone_all_nodes(complex_list_node *head) {
    complex_list_node *node, *clone;
    node = head;
    while (node) {
        // 拷贝节点
        clone = new complex_list_node{node->val, node->next, node->random};
        // 修改p节点的下一个节点指向新创建的节点
        node->next = clone;
        // 移动p指针到下一个节点
        node = clone->next;
    }
}

第二步修改random指针指向:

/*
 * 修正所有克隆节点的随机指针的地址
 * @head 链表的头结点
 */
static void fix_random_nodes(complex_list_node *head) {
    // node/q节点分别表示原始节点和被克隆的节点
    complex_list_node *node, *clone;
    node = head;
    while (node) {
        clone = node->next;
        // 修正q节点的random指针,注意random可能为null
        if (clone->random)
            clone->random = clone->random->next;
        node = clone->next;
    }
}

第三步分离原始链表和新克隆链表:

/*
 * 修改新复制节点的指向,重新连接所有节点
 * @head 头结点指针
 * @return 新复制的链表头结点
 */
static complex_list_node *reconnect_nodes(complex_list_node *head) {
    complex_list_node *node, *clone;
    complex_list_node *new_head = head->next;

    node = head;
    while (node) {
        clone = node->next;
        // 备份下一个节点的指针
        node->next = clone->next;
        // 连接到新克隆的节点,注意next可能为null
        if (clone->next) {
            clone->next = clone->next->next;
        }
        node = node->next;
    }
    return new_head;
}

整合起来的复杂链表拷贝函数:

complex_list_node *clone_complex_list(complex_list_node *head) {
    if (head == nullptr)
        return nullptr;
    clone_all_nodes(head);
    fix_random_nodes(head);
    return reconnect_nodes(head);
}

注意:这个函数里面一定要判断head是否为空的情况,前面三个函数因为是内部函数所以没有在入口判断head为空的情况!

四、单元测试

写代码容易,写单元测试麻烦,一个单元测试写了一小时。。。

4.1 链表创建函数

做单元测试,必不可少的一个测试函数是链表创建函数,因为要测试链表,首先要先创建链表:

/*
 * 链表创建函数
 * @val 数据数组
 * @random 随机指针
 * @n 数组数量
 * @return 返回链表头
 */
static complex_list_node *list_creator(int data[], int random[], int n) {
    complex_list_node *head, **nodes, *p;
    int i;
    nodes = new p_complex_list_node[n];

    // 创建所有的链表节点
    for (i = 0; i < n; i++) {
        p = new complex_list_node{data[i], nullptr, nullptr};
        nodes[i] = p;
    }

    // 连接所有的next和random指针
    for (i = 0; i < n - 1; i++) {
        nodes[i]->next = nodes[i + 1];
        if (random[i] == -1) {
            nodes[i]->random = nullptr;
        } else {
            nodes[i]->random = nodes[random[i]];
        }
    }
    nodes[i]->random = nodes[random[i]];

    p = nodes[0];
    delete nodes;

    return p;
}

测试这个函数的正确性:

// 测试创建链表的功能是不是OK
TEST(complex_list_node, list_creator) {
    // 数据数组
    int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    // 随机指针数组
    int random[10] = {2, 3, 6, -1, 3, 1, 0, 9, 5, 3};
    int i;
    complex_list_node *head, *p, *r, *n;
    // 创建链表
    head = list_creator(arr, random, 10);
    p = head;
    for (i = 0; i < 10 && p; i++, p = p->next) {
        r = p->random;
        EXPECT_EQ(p->val, arr[i]);
        if (random[i] == -1) {
            EXPECT_EQ(r, nullptr);
        } else {
            EXPECT_EQ(r->val, arr[random[i]]);
        }
    }
}

4.1 测试功能

测试功能点:

  1. 空链表测试
  2. 非空链表测试
  3. 链表克隆后是否会影响原始链表

leetcode上也有这个一模一样的题目,下面的测试数据是基于leetcode来写的。

https://leetcode-cn.com/problems/copy-list-with-random-pointer/submissions/

TestFixtures定义:

const int leetcode_demo1_cnt = 5;
class leetcode_test : public ::testing::Test {
protected:
    void SetUp() override {
        // 初始化list1
        int arr[leetcode_demo1_cnt] = {7, 13, 11, 10, 1};
        int random[leetcode_demo1_cnt] = {-1, 0, 4, 2, 0};
        list1 = list_creator(arr, random, leetcode_demo1_cnt);
        // list2为空
        list2 = nullptr;
    }

    void TearDown() override {
        complex_list_node *p = list1, *next;
        while (p) {
            next = p->next;
            delete p;
            p = next;
        }
    }

    complex_list_node *list1;
    complex_list_node *list2;
};

测试克隆链表案例:

TEST_F(leetcode_test, clone_nodes) {
    int i;
    complex_list_node *clone, *p, *q;
    clone = clone_complex_list(list1);

    p = list1;
    q = clone;
    // 遍历所有节点,所有节点的内容和原节点一样,并且克隆出来的节点并不是原链表上面的指针
    for (i = 0; i < leetcode_demo1_cnt && p && q; i++, p = p->next, q = q->next) {
        // 两个节点不相等
        EXPECT_NE(p, q);
        // 节点的值都一样
        EXPECT_EQ(p->val, q->val);
        if (p->next) {
            EXPECT_TRUE(q->next);
            EXPECT_NE(p->next, q->next);
        }
        if (p->random) {
            EXPECT_TRUE(q->random);
            EXPECT_NE(q->random, p->random);
        }
    }
    // 都遍历的链表结尾了
    EXPECT_FALSE(p);
    EXPECT_FALSE(q);
    EXPECT_EQ(i, leetcode_demo1_cnt);
}

测试空链表复制:

TEST_F(leetcode_test, empty_list) {
    complex_list_node *clone;
    clone = clone_complex_list(list2);
    EXPECT_FALSE(clone);
}

测试是否修改了原链表:

TEST_F(leetcode_test, origin_list_not_modify) {
    int i = 0;
    complex_list_node *bak[leetcode_demo1_cnt], *p, *clone;
    // 备份原始链表的所有next指针
    p = list1;
    while (p) {
        bak[i++] = p->next;
        p = p->next;
    }

    clone = clone_complex_list(list1);

    // 和克隆前的next指针对比,确认没有影响到原始指针
    p = list1;
    i = 0;
    while (p) {
        EXPECT_EQ(bak[i++], p->next);
        p = p->next;
    }
}

测试结果:

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

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

例如:给定一个链表: 1->2->3->4->5, 和 n = 2。当删除了倒数第二个节点后,链表变为 1->2->3->5。

说明:

给定的 n 保证是有效的。

进阶:你能尝试使用一趟扫描实现吗?

二、题解

使用快慢指针,快指针先走n步,满指针再走,当快指针为空的时候满指针就是倒数第n个节点。

《剑指offer》面试题22:链表中的倒数第k个节点类似,知识剑指offer中是返回倒数第n个节点,这道题目是删除倒数第n各节点,多了个删除操作。

三、代码

class Solution {
public:
    ListNode *removeNthFromEnd(ListNode *head, int n) {
        ListNode *prev, *fast, *slow;
        prev = nullptr;
        fast = head;
        slow = head;

        // 快指针先走n步
        while (fast && n--)
            fast = fast->next;

        // n大于链表长度,直接返回head
        if (!fast && n > 0)
            return head;

        // 遍历快慢指针,并备份慢指针
        while (fast != nullptr) {
            prev = slow;
            fast = fast->next;
            slow = slow->next;
        }

        // 删除节点,注意删除的节点可能是链表收尾节点
        if (prev) {
            // 如果删除的是最后一个节点,slow为空
            prev->next = slow ? slow->next : nullptr;
            return head;
        } else {
            // 删除的首节点,返回首节点的下一个节点
            return head->next;
        }
    }
};

一、题目

输入一个链表,输出该链表中倒数第k个节点,为了符合大多数人的习惯,k的序号从1开始,即链表的尾结点是倒数第一个节点。

例如,如下链表中的倒数第3个节点是3

二、解题思路

使用快慢指针,快指针先走n步,然后快慢指针同时走,直到快指针为null,慢指针就是倒数第n个节点。

以上面的链表为例,初始时,两个指针都指向链表开头:

fast指针先往前走三步:

此时,fast和slow同时往前走,直到fast为空:

此时slow所在的位置刚好就是倒数第三个节点。

三、代码

代码中需要注意的问题:

  1. 链表可能为空,避免空指针访问。
  2. 链表长度可能不够n,遍历fast的时候注意判断非空,并且外部应该判断出这一点。
list_node *find_kth_to_tail(list_node *head, int n) {
    list_node *slow, *fast;
    
    // [1] 注意链表为空的情况
    if (head == nullptr)
        return nullptr;

    fast = head;
    slow = head;

    // [2] fast指针先走n步
    while (fast && n--) {
        fast = fast->next;
    }

    // [3] 整个链表长度不够n
    if (!fast && n > 0)
        return nullptr;

    while (fast) {
        fast = fast->next;
        slow = slow->next;
    }

    return slow;
}

四、单元测试

单元测试主要覆盖以下几点:

  1. 链表为空的场景。
  2. 链表不为空,获取倒数第1个节点。
  3. 链表不为空,获取倒数第n个节点,n等于链表长度。
  4. 链表不为空,获取倒数第n个节点,n大于链表长度。

TestFixtures定义了三个链表,第一个链表有5个元素,第二个链表为空,第三个链表有1个元素。

class list_test : public ::testing::Test {
protected:
    void SetUp() override {
        int i, arr[] = {2, 3, 4, 5};
        // 初始化链表1
        list_node *p;
        p = new list_node{1, nullptr};
        list1 = p;
        for (i = 0; i < 4; i++) {
            p->next = new list_node{arr[i], nullptr};
            p = p->next;
        }
        // 初始化链表2
        list2 = nullptr;
        // 初始化链表3
        list3 = new list_node{1, nullptr};
    }

    // 释放链表节点
    void TearDown() override {
        list_node *p, *next;
        p = list1;
        while (p) {
            next = p->next;
            delete p;
            p = next;
        }
        delete list3;
        list3 = nullptr;
    }

    // 初始化三个链表,第一个链表有5个节点,第二个链表为空,第三个链表只有一个节点
    list_node *list1;
    list_node *list2;
    list_node *list3;
};

测试案例一:获取倒数第n个节点,n分别大于、小于和等于链表长度。

// 案例:获取倒数第n个节点,n小于链表长度
TEST_F(list_test, multi_node) {
    list_node *p;
    // 倒数第一个节点
    p = find_kth_to_tail(list1, 1);
    EXPECT_EQ(p->data, 5);
    // 倒数第二个节点
    p = find_kth_to_tail(list1, 2);
    EXPECT_EQ(p->data, 4);
    // 倒数第三个节点
    p = find_kth_to_tail(list1, 3);
    EXPECT_EQ(p->data, 3);
    // 倒数第四个节点
    p = find_kth_to_tail(list1, 4);
    EXPECT_EQ(p->data, 2);
    // 倒数第五个节点
    p = find_kth_to_tail(list1, 5);
    EXPECT_EQ(p->data, 1);
    // 倒数第六个节点 --> 空
    p = find_kth_to_tail(list1, 6);
    EXPECT_FALSE(p);
}

测试案例二:测试空链表。

// 案例:空链表测试
TEST_F(list_test, no_node) {
    list_node *p;
    p = find_kth_to_tail(list2, 1);
    EXPECT_FALSE(p);
    p = find_kth_to_tail(list2, 2);
    EXPECT_FALSE(p);
}

测试案例三:只有一个节点的链表测试。

// 案例:只有一个节点的链表测试
TEST_F(list_test, one_node) {
    list_node *p;
    // 倒数第一个节点,不为空
    p = find_kth_to_tail(list3, 1);
    EXPECT_EQ(p->data, 1);
    // 倒数第二个节点,空
    p = find_kth_to_tail(list3, 2);
    EXPECT_FALSE(p);
    // 倒数第三个节点,空
    p = find_kth_to_tail(list3, 3);
    EXPECT_FALSE(p);
}

测试结果:

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/range-sum-query-immutable

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。

示例:

给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()。

sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3

说明:

  1. 你可以假设数组不可变。
  2. 会多次调用 sumRange 方法。

二、题解

题目很迷幻,一眼看上去可能会误解成就是求指定区间的和,如果只是求指定区间内的和,除了遍历就没有其他办法了。而实际上整道题目的重点就是会多次调用,也就是说同一份数据,会有多个测试案例。

很容易能猜出最容易出现问题的地方就是超时了,因为实际的问题很简单,就是求和,求和只能遍历,不可能就这么简单每次就遍历求和。因此考虑的重点就在于如何在多次调用中减少时间占用。

减少计算时间的办法:缓存数据,即缓存下来部分数据的和,再根据缓存的和来计算特定范围的和。

以测试数据为例,以sum[i]表示下标i以前(不包括i)的所有数据之和,那么sum数组的值为:

0, -2, -2, 1, -4, -2, -3

sumRange(0, 2)就相当于计算sum[2 + 1] - sum[0] ,值为1。

sumRange(2, 5)就相当于计算sum[5 + 1] - sum[2],值为-2。

在这种情况下能总结出来的规律是:sumRange(i, j) = sum[j + 1] - sum[i]。

三、代码

逻辑很简单,但关键点是:sum[i]表示的是i以前所有元素的和,所以要给sum[0]赋值上一个0值。

class NumArray {
private:
    vector<int> sum;
public:
    NumArray(vector<int>& nums) {
        // 构造函数中初始化sum数组
        size_t i;
        // 给sum[0]附上一个0值
        sum.push_back(0);
        for (i = 0; i< nums.size(); i++) {
            sum.push_back(sum[i] + nums[i]);
        }
    }

    int sumRange(int i, int j) {
        return sum[j + 1] - sum[i];
    }
};

优化

可以看到,时间消耗还是很大的,排名才击败了38%。代码中主要的耗时是vector数组的扩容导致的,因为够早的时候需要频繁push_back导致数组扩容和内存拷贝,比较费时。可以把vector改成数组形式,手动分配内存空间,减少vector频繁扩容。

class NumArray {
private:
    int *sum;
public:
    NumArray(vector<int>& nums) {
        size_t i;
        sum = new int[nums.size() + 1];
        sum[0] = 0;
        for (i = 0; i< nums.size(); i++) {
            sum[i + 1] = sum[i] + nums[i];
        }
    }

    int sumRange(int i, int j) {
        return sum[j + 1] - sum[i];
    }
};

重新提交后,时间消耗击败了86%的用户。

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/binary-search

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的 target,如果目标值存在返回下标,否则返回-1

示例1:

  • 输入:nums = [-1,0,3,5,9,12], target = 9
  • 输出:4
  • 解释:9出现在nums中并且下标为 4

示例 2:

  • 输入:nums = [-1,0,3,5,9,12], target = 2
  • 输出:-1
  • 解释:2不存在nums中因此返回 -1

提示:

  1. 你可以假设 nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000]之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。

二、二分查找

二分查找的前提要求是数组有序,一趟二分查找的算法描述为:

  1. 确定数组上下边界,然后取中间值mid。
  2. 对比中间值mid和目标值target,此时有两种情况:

    • 如果target小于mid,说明目标值在下边界区,继续在下半边界执行步骤1。
    • 如果target大于mid,说明目标值在上边界区,继续在上半边界执行步骤1。
  3. 如果上下边界一直找完都没有找到目标值,说明数组中不存在指定数据。

图文描述

在数组[2, 3, 4, 5, 6, 7, 8]中查找3。第一步,计算mid是下标为3的值5,此时5比目标值3要大,说明3出现在5左边,要在[low, mid)进行下一步查找:

第二步,重新定位修改区域,查找的下标范围是[0, 2]。此时mid计算出来等于1,下标等于1的元素值为3,刚好就是我们要找的目标值,查找完成:

三、代码

需要注意的点已经在代码中标出:

  1. 注意有符号、无符号以及数据是否会溢出。当前题目中数据不大,所以直接使用int。
  2. 循环结束的条件是low > high,所以循环条件一定是low <= high,不要少了等于符号。
  3. 没有找到目标值的时候,返回-1。
这么简单的一个题目,提交了4次才通过。。。。基础很重要!!!
/*
* 二分查找法法
* @nums 数组
* @target 待查找的目标元素
* @return 找到返回数组下标,没找到返回-1
*/
int search(vector<int> &nums, int target) {
    // [1] 注意数据溢出
    int low, high, mid;

    low = 0;
    high = nums.size() - 1;

    // [2] 判断条件是小于等于,而不是小于
    while (low <= high) {
        mid = (low + high) / 2;
        if (target < nums[mid]) {
            high = mid - 1;
        } else if (target > nums[mid]) {
            low = mid + 1;
        } else {
            return mid;
        }
    }
    // [3] 没找到默认返回-1
    return -1;
}

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/paint-fence

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

有 k 种颜色的涂料和一个包含 n 个栅栏柱的栅栏,每个栅栏柱可以用其中一种颜色进行上色。

你需要给所有栅栏柱上色,并且保证其中相邻的栅栏柱 最多连续两个 颜色相同。然后,返回所有有效涂色的方案数。

注意:n 和 k 均为非负的整数。

示例

  • 输入:n = 3,k = 2
  • 输出:6
  • 解析:用 c1 表示颜色 1,c2 表示颜色 2,所有可能的涂色方案有

                柱 1    柱 2   柱 3     
     -----      -----  -----  -----       
       1         c1     c1     c2 
       2         c1     c2     c1 
       3         c1     c2     c2 
       4         c2     c1     c1  
       5         c2     c1     c2
       6         c2     c2     c1

二、题解

leetcode标注是简单题目,但是对我这种脑袋不太灵光的人来说,并不太简单,所以多参考了几种解题思路。

2.1 递推

假设前一个栅栏的颜色数量为f(n - 1),再前一个栅栏的颜色数量为f(n - 2

递推的思路:

  1. 如果当前栅栏颜色和前一个栅栏颜色一样,它的选择是k - 1(因为当前栅栏和前一个栅栏颜色一样,所以前一个栅栏颜色和更前一个展览的颜色不能一样,少一种颜色选择),这种情况的颜色数量为f(n - 2) * (k - 1)
  2. 如果当前栅栏颜色和前一个栅栏颜色不一样,那么当前栅栏的颜色选择就只有k - 1种,总的颜色数量为f(n - 1) * (k - 1)

总结出来的递推公式:f(n) = f(n - 1) * (k - 1) + f(n - 2) * (k - 1)

代码:

class Solution {
public:
     int numWays(int n, int k) {
        int prev, pprev, rs, i;
        if (n <= 0 || k <= 0)
            return 0;

        if (n == 1)
            return k;

        pprev = k;
        prev = k * k;
        rs = prev;
        for (i = 2; i < n; i++) {
            rs = pprev * (k - 1) + prev * (k - 1);
            pprev = prev;
            prev = rs;
        }

        return rs;
    }
};

2.2 动态规划

以前面两个栅栏的颜色来确定当前栅栏的颜色:

  1. 如果前两个栅栏的颜色相同,那么当前栅栏的颜色有k - 1种。
  2. 如果前两个栅栏的颜色不同,那么当前栅栏的颜色有k种。

分别以dp1和dp2保存颜色相同和不同的种数,可以总结出来的状态转移方程:

  • dp1 = prev_dp1
  • dp2 = (k - 1) * (dp1 + dp2)

dp1是相同的颜色数量,它和上一个栅栏颜色相同,所以就等于上一个栅栏颜色数量。dp2是不同的颜色数量,它等于上一个栅栏颜色总量的和乘以k - 1

代码:

class Solution {
public:
    int numWays(int n, int k) {
        int dp1, dp2, rs, i;
        if (n <= 0 || k <= 0)
            return 0;

        if (n == 1)
            return k;

        dp1 = k;
        dp2 = k * (k - 1);
        for (i = 2; i < n; i++) {
            rs = (dp1 + dp2) * (k - 1);
            dp1 = dp2;
            dp2 = rs;
        }
        return dp1 + dp2;
    }
};

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/binary-tree-postorder-traversal

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

给定一个二叉树,返回它的 后序 遍历。

例如输入[1,null,2,3] :

   1
    \
     2
    /
   3 

输出:

[3,2,1]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?

二、题解

参考:二叉树的后序遍历

三、代码

3.1 通过栈+链表实现

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode *> s;
        list<TreeNode *> l;
        vector<int> v;
        
        TreeNode *p;
        if (root == nullptr) {
            return v;
        }
        
        s.push(root);
        while (!s.empty()) {
            p = s.top();
            s.pop();
            l.push_front(p);
            if (p->left) {
                s.push(p->left);
            }
            if (p->right) {
                s.push(p->right);
            }
        }
        
        while(!l.empty()) {
            v.push_back(l.front()->val);
            l.pop_front();
        }
        
        return v;
    }
};

3.2 通过2次压栈实现

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        TreeNode *p;
        vector<int> v;
        stack<TreeNode *> st;
        if (root == nullptr) {
            return v;
        }
        
        st.push(root); st.push(root);
        while (!st.empty()) {
            p = st.top();
            st.pop();
            if (!st.empty() && st.top() == p) {
                if (p->right) {
                    st.push(p->right); st.push(p->right);
                }
                if (p->left) {
                    st.push(p->left); st.push(p->left);
                }
            } else {
                v.push_back(p->val);
            }
        }

        return v;
    }
};