牛客《剑指Offer》刷题笔记

剑指Offer

  • 题目中的共性问题
  • 数组中重复的数字
  • 二维数组中的查找
  • 替换空格
  • 从头到尾打印链表
  • 重建二叉树
  • 二叉树的下一个节点
  • 用两个栈实现队列
  • 斐波那契数列
  • 旋转数组的最小数字
  • 矩阵中的路径
  • 机器人的运动范围
  • 剪绳子
  • 二进制中1的个数
  • 数值的整数次方
  • 打印从1到最大的n位数
  • 删除链表节点
  • 正则表达式匹配
  • 表示数值的字符串
  • 调整数组顺序使奇数位于偶数前面(一)
  • 链表中倒数最后k个结点
  • 链表中环的入口
  • 反转链表
  • 合并两个排序的链表
  • 树的子结构
  • 二叉树的镜像
  • 对称的二叉树
  • 顺时针打印矩阵

题目中的共性问题

  • 注意考虑特殊情况,如题目输入的数据为空等。

数组中重复的数字

  • 题目描述:在一个长度为 n n n的数组里的所有数字都在 0 0 0 n − 1 n-1 n1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为 7 7 7的数组 [ 2 , 3 , 1 , 0 , 2 , 5 , 3 ] [2,3,1,0,2,5,3] [2,3,1,0,2,5,3],那么对应的输出是 2 2 2或者 3 3 3。存在不合法的输入的话输出 − 1 -1 1
  • 解题思路:简单题。维护一个 c o u n t e r counter counter数组, c o u n t e r [ 0 ] = 0 counter[0]=0 counter[0]=0代表数字 0 0 0出现的次数为 0 0 0,以此类推。遍历原数组,读取数字后将其对应的 c o u n t e r counter counter 1 1 1,如发现该数字已出现,则返回该数字。
  • C++代码
#include 
#include 
using namespace std;

int duplicate(vector<int>& numbers) {
    vector<int> counter(numbers.size(), 0);
    for(int i = 0; i < numbers.size(); ++i) {
        if(counter[numbers[i]] >= 1) {
            return numbers[i];
        }
        else {
            ++counter[numbers[i]];
        }
    }
    return -1;
}

int main(int argc, const char * argv[]) {
    vector<int> numbers{2, 3, 1, 0, 6, 5, 4};
    cout << duplicate(numbers) << endl;
    return 0;
}

二维数组中的查找

  • 问题描述:在一个二维数组 a r r a y array array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
  • 解题思路:这题比较容易陷入一个误区——因为左到右、上到下是递增的,就按照从左上角到右下角(或相反)的顺序查找。为什么说这是误区呢?如果当前数字小于被查找数,但是我们知道当前位置右侧和下侧的两个数都是大于当前数字的,因此我们无法判断是要往下走还是往右走。因此,我们应该从右上角开始查找,这样能保证左侧的数字都比当前数字小、下侧的数字都比当前数字,故当前数字与被查找数比较之后,可以很清楚地明白是要往哪边继续查找。从左下角开始同理。这样的话时间复杂度就是 O ( n + m ) O(n+m) O(n+m),空间复杂度是 O ( 1 ) O(1) O(1)
  • 源代码
#include 
#include 
using namespace std;

bool find(int target, vector<vector<int>> numbers) {
    // 当前行、列
    int x = 0, y = (int)(numbers[x].size() - 1);
    while(x < numbers.size() && x >=0 && y <numbers[x].size() && y >= 0) {
        if(numbers[x][y] == target) {
            return true;
        }
        else if(numbers[x][y] < target) {
            ++x;
        }
        else {
            --y;
        }
    }
    return false;
}

int main(int argc, const char * argv[]) {
    vector<vector<int>> numbers{{1, 3, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}};
    cout << find(0, numbers) << endl;
    return 0;
}

替换空格

  • 题目描述:请实现一个函数,将一个字符串s中的每个空格替换成“%20”。
    例如,当字符串为We Are Happy,则经过替换之后的字符串为We%20Are%20Happy
  • 解题思路:简单题。遍历字符串中的每个字符,判断是不是空格就够了。需要注意的是,比较空格时应该是s == ' '而不是s == " "
  • 源代码
#include 
#include 
using namespace std;

string repalceSpace(string s) {
    string ns = "";
    for(int i = 0; i < s.length(); ++i) {
        if(s[i] == ' ') {
            ns += "%20";
        }
        else {
            ns += s[i];
        }
    }
    return ns;
}

int main(int argc, const char * argv[]) {
    string s = "We are happy.";
    cout << repalceSpace(s) << endl;
    return 0;
}

从头到尾打印链表

  • 题目描述:输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。
  • 解题思路:不知道为什么会出这么简单的题目…不过也算是对vector有了新的认识…
  • 源代码
#include 
#include 
using namespace std;

struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

vector<int> printListFromTailToHead(ListNode* head) {
    vector<int> result;
    ListNode* current = head;
    while(current != NULL) {
        result.insert(result.begin(), current->val);
        current = current->next;
    }
    return result;
}

int main(int argc, const char * argv[]) {
    ListNode* n1 = new ListNode(2);
    ListNode* n2 = new ListNode(1);
    ListNode* n3 = new ListNode(3);
    ListNode* n4 = new ListNode(0);
    ListNode* n5 = new ListNode(10);
    n1->next = n2;
    n2->next = n3;
    n3->next = n4;
    n4->next = n5;
    vector<int> result = printListFromTailToHead(n1);
    for(auto i : result) {
        cout << i << endl;
    }
    delete n1, n2, n3, n4, n5;
    return 0;
}


重建二叉树

  • 题目描述:给定节点数为 n n n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。例如输入前序遍历序列 p r e = [ 1 , 2 , 4 , 7 , 3 , 5 , 6 , 8 ] pre={[1,2,4,7,3,5,6,8]} pre=[1,2,4,7,3,5,6,8] 和中序遍历序列 = [ 4 , 7 , 2 , 1 , 5 , 3 , 8 , 6 ] ={[4,7,2,1,5,3,8,6]} =[4,7,2,1,5,3,8,6] ,则重建出 [ 1 , 2 , 3 , 4 , # , 5 , 6 , # , 7 , # , # , 8 ] {[1,2,3,4,\#,5,6,\#,7,\#,\#,8]} [1,2,3,4,#,5,6,#,7,#,#,8]
  • 解题思路分治法
    前序遍历即按照根节点、左子节点、右子节点的顺序放问树,因此对于一个前序遍历序列,第一个元素一定是根节点
    中序遍历即按照左子节点、根节点、右子节点的顺序访问树,因此,根据前序遍历序列我们知道了根节点,那么在中序遍历序列中,位于根节点左侧的点肯定在左子树中,位于根节点右侧的点肯定在右子树中
    基于上述两个高亮的结论,我们就能把问题分解为构造出根节点的左子树和右子树这两个子问题
    其中需要注意的是,在前序遍历序列中,紧跟着根节点后的若干个元素是左子树的前序遍历序列,剩下的是右子树的前序遍历队列,数量可以由中序遍历序列获得。
  • 源代码
#include 
#include 
#include 
using namespace std;

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
    // 边界情况检查
    if(pre.empty() || vin.empty()) {
        return NULL;
    }

    // 前序遍历序列第一个元素一定是根节点
    int rootValue = pre[0];
    TreeNode* root = new TreeNode(rootValue);
    // 在中序遍历序列中找根节点
    vector<int>::iterator rootIndexInVin = find(vin.begin(), vin.end(), rootValue);
    int leftPreLen = rootIndexInVin - vin.begin();
    // 构造左右子树的前序遍历序列和中序遍历序列
    if(leftPreLen > 0) {
        vector<int> leftPre(pre.begin() + 1, pre.begin() + 1 + leftPreLen);
        vector<int> leftVin(vin.begin(), vin.begin() + leftPreLen);
        root->left = reConstructBinaryTree(leftPre, leftVin);
    }
    if(leftPreLen < vin.size() - 1) {
        vector<int> rightPre(pre.begin() + 1 + leftPreLen, pre.end());
        vector<int> rightVin(vin.begin() + 1 + leftPreLen, vin.end());
        root->right = reConstructBinaryTree(rightPre, rightVin);
    }
    
    return root;
}

int main(int argc, const char * argv[]) {
    
    vector<int> pre{1,2,4,7,3,5,6,8};
    vector<int> vin{4,7,2,1,5,3,8,6};
    TreeNode* root = reConstructBinaryTree(pre, vin);
    return 0;
}

二叉树的下一个节点

  • 题目描述:给定一个二叉树其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的next指针。

  • 解题思路中序遍历即按照左子节点、根节点、右子节点的顺序访问树。因此:
    如果该节点有右子树,那么该节点的下一个节点右子树中的最左节点
    如果该节点没有右子树,则该节点的下一个节点是——该节点的祖父节点集合中属于其父节点的左节点的点的父节点,例如下图中的节点 i i i,节点 b , e , a b,e,a b,e,a都是其祖父节点,往上一层层寻找的过程中,发现第一组出现父节点、左子节点的关系的节点是 b b b a a a,因此 a a a i i i的下一个前序遍历节点。

    当然还有一种很笨的方法,就是直接找到根节点,然后在前序遍历队列中查找。

牛客《剑指Offer》刷题笔记_第1张图片

  • 源代码
#include 
using namespace std;

struct TreeLinkNode {
    int val;
    struct TreeLinkNode *left;
    struct TreeLinkNode *right;
    struct TreeLinkNode *next;
    TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {}
};

TreeLinkNode* GetNext(TreeLinkNode* pNode) {
    // 边界情况检查
    if(pNode == NULL) {
        return NULL;
    }
    // 有右子树,那么该节点的下一个节点右子树中的最左节点
    if(pNode->right != NULL) {
        pNode = pNode->right;
        while(pNode->left != NULL) {
            pNode = pNode->left;
        }
        return pNode;
    }
    // 没有右子树,则该节点的下一个节点是该节点的父节点集合中属于其父节点的左节点的点的父节点
    else {
        while(pNode->next != NULL) {
            if(pNode->next->left == pNode) {
                return pNode->next;
            }
            else {
                pNode = pNode->next;
            }
        }
    }
    // 查找到树的根节点都找不到的话就没了
    return NULL;
}

int main(int argc, const char * argv[]) {
    TreeLinkNode* a = new TreeLinkNode(0);
    TreeLinkNode* b = new TreeLinkNode(1);
    TreeLinkNode* c = new TreeLinkNode(2);
    TreeLinkNode* d = new TreeLinkNode(3);
    TreeLinkNode* e = new TreeLinkNode(4);
    TreeLinkNode* f = new TreeLinkNode(5);
    TreeLinkNode* g = new TreeLinkNode(6);
    TreeLinkNode* h = new TreeLinkNode(7);
    TreeLinkNode* i = new TreeLinkNode(8);
    a->left = b;
    a->right = c;
    b->left = d;
    b->right = e;
    b->next = a;
    c->left = f;
    c->right = g;
    c->next = a;
    d->next = b;
    e->left = h;
    e->right = i;
    e->next = b;
    f->next = c;
    g->next = c;
    h->next = e;
    i->next = e;
    cout << GetNext(i)->val << endl;
    return 0;
}

用两个栈实现队列

  • 题目描述:用两个栈来实现一个队列,使用n个元素来完成 n 次在队列尾部插入整数(push)和n次在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。
  • 解决思路:需要明白队列是First In First Out,而栈是First In Last Out。因此,stack1用来输入数据,即完成push操作。stack2用来输出数据,即需要pop时,将stack1中的数据pop出来存到stack2中,相当于将stack1反过来了,这时候stack2进行pop就相当于FIFO了。
  • 源代码
#include 
#include 
using namespace std;

stack<int> stack1;
stack<int> stack2;

void push(int node) {
    stack1.push(node);
}

int pop() {
    if(stack2.empty()) {
        while (!stack1.empty()) {
            stack2.push(stack1.top());
            stack1.pop();
        }
    }
    int node = stack2.top();
    stack2.pop();
    return node;
}

int main(int argc, const char * argv[]) {
    push(0);
    push(1);
    cout << pop() << endl;
    cout << pop() << endl;
    push(2);
    cout << pop() << endl;
    push(3);
    cout << pop() << endl;
    push(4);
    push(5);
    push(6);
    push(7);
    cout << pop() << endl;
    cout << pop() << endl;
    push(8);
    push(9);
    cout << pop() << endl;
    cout << pop() << endl;
    cout << pop() << endl;
    cout << pop() << endl;
    return 0;
}

斐波那契数列

  • 题目描述:输入一个正整数 n n n ,请你输出斐波那契数列的第 n n n 项。
  • 解决思路:简单题,一个for循环解决。
  • 源代码
class Solution {
  public:
    int Fibonacci(int n) {
        if (n > 2) {
            int a = 0, b = 1, c = 1, d; //第i-2,i-2,i个数和中间量
            for (int i = 3; i <= n; i++) {
                d = c;
                c = c + b;
                a = b;
                b = d;
            }
            return c;
        } else if (n == 1) {
            return 0;
        } else {
            return 1;
        }
    }
};

旋转数组的最小数字

  • 题目描述:有一个长度为 n n n 的非降序数组,比如 [ 1 , 2 , 3 , 4 , 5 ] [1,2,3,4,5] [1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了 [ 3 , 4 , 5 , 1 , 2 ] [3,4,5,1,2] [3,4,5,1,2],或者 [ 4 , 5 , 1 , 2 , 3 ] [4,5,1,2,3] [4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。要求时间复杂度为 O ( l o g n ) O(logn) O(logn)
  • 解决思路:首先不是被最小值迷惑了,其实就是找到一对 a r r a y [ i ] > a r r a y [ i + 1 ] array[i] > array[i+1] array[i]>array[i+1]。其次,看到这个时间要求,就立马想到了二分法。因此,把数组分成左右两边:(0)如果边界就出现了 a r r a y [ m i d ] > a r r a y [ m i d + 1 ] array[mid] > array[mid+1] array[mid]>array[mid+1],那么就找到最小值了;(1)如果左边的最后一个数大于等于右边的最后一个数,那么最小值肯定在右侧数组;(2)如果左边的最后一个数小于右边的最后一个数,那么最小值肯定在左侧数组;(3)如果左边的最后一个数等于右边的最后一个数,这个情况比较特殊,无法确定最小值在哪,可以让数组右侧边界慢慢减少。要注意数组根本没有旋转的情况。
  • 源代码
#include 
#include 
using namespace std;

int minNumberInRotateArray(vector<int> rotateArray) {
    // 检测非法输入
    if(rotateArray.empty()) {
        return 0;
    }
    if(rotateArray[0] < rotateArray[rotateArray.size() - 1]) {
        return rotateArray[0];
    }
    
    // 子问题
    if(rotateArray.size() == 1) {
        return 0;
    }
    if(rotateArray.size() == 2) {
        if(rotateArray[0] > rotateArray[1]) {
            return rotateArray[1];
        }
        else {
            return 0;
        }
    }
    
    // 分割数组
    int right = rotateArray.size() / 2;
    int left = right - 1;
    int last = rotateArray.size() - 1;
    
    // 终止条件
    if(rotateArray[left] > rotateArray[right]) {
        return rotateArray[right];
    }
    
    // 二分法
    // 如果左边的最后一个数大于等于右边的最后一个数,那么最小值肯定在右侧数组
    if (rotateArray[left] > rotateArray[last]) {
        vector<int> newRotateArray(rotateArray.begin() + right, rotateArray.end());
        return minNumberInRotateArray(newRotateArray);
    }
    // 如果左边的最后一个数小于右边的最后一个数,那么最小值肯定在左侧数组
    else if (rotateArray[left] < rotateArray[last]){
        vector<int> newRotateArray(rotateArray.begin(), rotateArray.begin() + left + 1);
        return minNumberInRotateArray(newRotateArray);
    }
    // 如果左边的最后一个数等于右边的最后一个数,不能判断最小值在左在右,可使用缩减边界法
    else {
        while(rotateArray[left] == rotateArray[last] && left < last) {
            --last;
        }
        vector<int> newRotateArray(rotateArray.begin(), rotateArray.begin() + last + 1);
        return minNumberInRotateArray(newRotateArray);
    }
}

int main(int argc, const char * argv[]) {
    vector<int> array0{3, 4, 5, 1, 2};
    vector<int> array1{1, 1, 1, 1, 0, 1};
    vector<int> array2{2, 2, 2, 1, 2};
    vector<int> array3{1, 1, 2, 2, 2, 2, 2, 1};
    vector<int> array4{2, 2, 1, 2, 2, 2};
    vector<int> array5{1, 0, 1, 1, 1, 1};
    cout << minNumberInRotateArray(array0) << endl;
    cout << minNumberInRotateArray(array1) << endl;
    cout << minNumberInRotateArray(array2) << endl;
    cout << minNumberInRotateArray(array3) << endl;
    cout << minNumberInRotateArray(array4) << endl;
    cout << minNumberInRotateArray(array5) << endl;
    return 0;
}

矩阵中的路径

  • 题目描述:请设计一个函数,用来判断在一个 n n n m m m的矩阵中是否存在一条包含某长度为 l e n len len的字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。
  • 解题思路:先找到第一个字母的位置。之后用BFS或者DFS都可以。注意不能走重复的格子。
  • 源代码
#include 
#include 
#include 
#include 
using namespace std;

// 深度优先搜索
bool search(vector<vector<char>>& matrix, string word, int x, int y, vector<vector<bool>> searched) {
    // 是否已经没有待查找元素
    if(word.empty()) {
        return true;
    }
    
    // 设置当前位置为已查找
    vector<vector<bool>> newSearched(searched);
    newSearched[x][y] = true;
    
    // 去掉字符串首元素
    char beginChar = word[0];
    string newWord(word.begin() + 1, word.end());
    
    // 向四个方向搜索
    if(x > 0) {
        if(!searched[x][y] && matrix[x - 1][y] == beginChar) {
            if(search(matrix, newWord, x - 1, y, newSearched)) {
                return true;
            }
        }
    }
    if(x < matrix.size() - 1) {
        if(!searched[x + 1][y] && matrix[x + 1][y] == beginChar) {
            if(search(matrix, newWord, x + 1, y, newSearched)) {
                return true;
            }
        }
    }
    if(y > 0) {
        if(!searched[x][y - 1] && matrix[x][y - 1] == beginChar) {
            if(search(matrix, newWord, x, y - 1, newSearched)) {
                return true;
            }
        }
    }
    if(y < matrix[x].size() - 1) {
        if(!searched[x][y + 1] && matrix[x][y + 1] == beginChar) {
            if(search(matrix, newWord, x, y + 1, newSearched)) {
                return true;
            }
        }
    }
    
    return false;
}

bool hasPath(vector<vector<char>>& matrix, string word) {
    // 非法检测
    if(matrix.empty() || word.empty()) {
        return false;
    }
    
    // 去掉字符串首元素
    char beginChar = word[0];
    string newWord(word.begin() + 1, word.end());
    
    // 构造已查找矩阵
    vector<vector<bool>> searched(matrix.size());
    for(int i = 0; i < matrix.size(); ++i) {
        vector<bool> temp(matrix[i].size(), false);
        searched[i] = temp;
    }
    
    // 先找到第一个字母的位置(x, y),可能有多个
    for(int i = 0; i < matrix.size(); ++i) {
        for(int j = 0; j < matrix[i].size(); ++j) {
            if(matrix[i][j] == beginChar) {
                vector<vector<bool>> newSearched(searched);
                if(search(matrix, newWord, i, j, newSearched)) {
                    return true;
                }
            }
        }
    }
    
    return false;
}

int main(int argc, const char * argv[]) {
    vector<vector<char>> matrix{{'a','b','c','e'},{'s','f','c','s'},{'a','d','e','e'}};
    cout << hasPath(matrix, "abcced") << endl;
    cout << hasPath(matrix, "abcb") << endl;
    return 0;
}

机器人的运动范围

  • 题目描述:地上有一个 r o w s rows rows 行和 c o l s cols cols 列的方格。坐标从 [ 0 , 0 ] [0,0] [0,0] [ r o w s − 1 , c o l s − 1 ] [rows-1,cols-1] [rows1,cols1] 。一个机器人从坐标 [ 0 , 0 ] [0,0] [0,0] 的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 t h r e s h o l d threshold threshold 的格子。 例如,当 t h r e s h o l d threshold threshold 18 18 18 时,机器人能够进入方格 [ 35 , 37 ] [35,37] [35,37] ,因为 3 + 5 + 3 + 7 = 18 3+5+3+7 = 18 3+5+3+7=18。但是,它不能进入方格 [ 35 , 38 ] [35,38] [35,38] ,因为 $3+5+3+8 = 19 。 请 问 该 机 器 人 能 够 达 到 多 少 个 格 子 ? 【 时 间 复 杂 度 和 空 间 复 杂 度 都 要 求 为 。请问该机器人能够达到多少个格子?【时间复杂度和空间复杂度都要求为 O(nm)$】
  • 解题思路:还是使用深度优先算法。但是与上一题不同的是,本题的记录是否已查找的矩阵对于所有分支来说是共享的。
  • 源代码
#include 
#include 
using namespace std;

vector<vector<bool>> counted;

bool judge(int threshold, int x, int y) {
    int count = 0;
    while(x != 0) {
        count += x % 10;
        x = x / 10;
    }
    while(y != 0) {
        count += y % 10;
        y = y / 10;
    }
    return count <= threshold;
}

int search(int threshold, int rows, int cols, int x, int y) {
    
    counted[x][y] = true;

    int count = 0;
    if(judge(threshold, x, y)) {
        ++count;
    }
    else {
        return count;
    }
    
    if(x > 0) {
        if(!counted[x - 1][y]) {
            count += search(threshold, rows, cols, x - 1, y);
        }
    }
    if(x < rows - 1) {
        if(!counted[x + 1][y]) {
            count += search(threshold, rows, cols, x + 1, y);
        }
    }
    if(y > 0) {
        if(!counted[x][y - 1]) {
            count += search(threshold, rows, cols, x, y - 1);
        }
    }
    if(y < cols - 1) {
        if(!counted[x][y + 1]) {
            count += search(threshold, rows, cols, x, y + 1);
        }
    }
    
    return count;
}

int movingCount(int threshold, int rows, int cols) {
    
    // 记录哪些格子已经被查找过了
    counted.clear();
    for(int i = 0; i < rows; ++i) {
        vector<bool> temp(cols, false);
        counted.push_back(temp);
    }
    
    return search(threshold, rows, cols, 0, 0);
}

int main(int argc, const char * argv[]) {
    cout << movingCount(1, 2, 3) << endl;
    cout << movingCount(0, 1, 3) << endl;
    cout << movingCount(5, 10, 10) << endl;
    return 0;
}

剪绳子

  • 题目描述:给你一根长度为 n 的绳子,请把绳子剪成整数长的 m m m 段( m m m n n n 都是整数, n > 1 n > 1 n>1 并且 m > 1 m > 1 m>1 m < = n m <= n m<=n ),每段绳子的长度记为 k [ 1 ] , . . . , k [ m ] k[1],...,k[m] k[1],...,k[m] 。请问 k [ 1 ] ∗ k [ 2 ] ∗ . . . ∗ k [ m ] k[1]*k[2]*...*k[m] k[1]k[2]...k[m] 可能的最大乘积是多少?例如,当绳子的长度是 8 8 8 时,我们把它剪成长度分别为 2 2 2 3 3 3 3 3 3 的三段,此时得到的最大乘积是 18 18 18 。【要求空间复杂度为 O ( 1 ) O(1) O(1) ,时间复杂度为 O ( n ) O(n) O(n)
  • 解题思路
    很明显的动态规划
    对于一条长为n的绳子,其分割成m段之后最大的段长度乘积 d p [ n ] = m a x ( d p [ n ] , d p [ i ] ∗ d p [ n − i ] ) , 2 < = i < = n dp[n] = max(dp[n], dp[i]*dp[n-i]), 2<=i<=n dp[n]=max(dp[n],dp[i]dp[ni]),2<=i<=n。需要注意的是,初始化时,不可以设置成 d p [ 1 ] = 1 , d p [ 2 ] = 1 , d p [ 3 ] = 2 dp[1]=1, dp[2]=1, dp[3]=2 dp[1]=1,dp[2]=1,dp[3]=2。这样设置看起来貌似符合题意,因为 2 = 1 + 1 2=1+1 2=1+1 3 = 1 + 2 3=1+2 3=1+2,但是会发现从 n = 4 n=4 n=4开始答案就是错的了,因为 4 = 2 + 2 4=2+2 4=2+2 4 = 1 + 3 4=1+3 4=1+3,按照这个初始条件会算出 d p [ 4 ] = 2 dp[4]=2 dp[4]=2,明显是错的。从数学角度分析,我们会发现所有数字都可以分成若干个 2 2 2 3 3 3,但是按照该题目条件 2 2 2 3 3 3的值是小于本身的( 1 1 1 2 2 2),因此 2 2 2 3 3 3的值应该设为其本身才利于之后的问题求解。
    然而呢,我实在想不出时复杂度为 O ( n ) O(n) O(n) 的解法,有懂的同学可以留言教一下。
  • 源代码
#include 
using namespace std;

int cutRope(int n) {
    // 一些最小子问题
    if(n == 2) {
        return 1;
    }
    else if(n == 3) {
        return 2;
    }
    
    // dp[i]代表了长度为i的绳子分割为m段之后最大的乘积
    int* dp = new int[n + 1];
    memset(dp, 0, (n + 1) * sizeof(int));
    dp[0] = 0;
    dp[1] = 1;
    dp[2] = 2;
    dp[3] = 3;
    
    // 开始动态规划
    for(int ni = 4; ni <= n; ++ni) {
        for(int nii = 1; nii < ni; ++nii) {
            dp[ni] = max(dp[ni], dp[nii] * dp[ni - nii]);
        }
    }
    
    // 删除资源并输出
    int result = dp[n];
    delete [] dp;
    return result;
}

int main(int argc, const char * argv[]) {
    cout << cutRope(3) << endl;
    cout << cutRope(5) << endl;
    cout << cutRope(6) << endl;
    cout << cutRope(8) << endl;
    return 0;
}

二进制中1的个数

  • 题目描述:输入一个整数 n ,输出该数32位二进制表示中1的个数。其中负数用补码表示。
  • 解决思路:对于非负数,很简单,一直处以2,统计余数是否为1就好了。对于负数,要了解补码怎么求:负数的补码 = {原码符号位不变} + {数值位按位取反后+1}。并且要注意最高位为符号位,0表示正数,1表示负数。
  • 源代码
#include 

int numOfBits = 32;

int  NumberOf1(int n) {
    int result = 0;
    
    // 正数
    if(n > 0) {
        while(n != 0) {
            if(n % 2 == 1) {
                ++result;
            }
            n = n / 2;
        }
    }
    // 负数
    else if(n < 0) {
        // 初始化编码数组
        int* code = new int[numOfBits];
        memset(code, 0, numOfBits * sizeof(int));
        // 符号位为1
        code[numOfBits - 1] = 1;
        // 求原码(与正数相同)
        n = -n;
        for(int i = 0; n != 0; ++i) {
            if(n % 2 == 1) {
                code[i] = 1;
            }
            n = n / 2;
        }
        // 求反码(除符号位外都取反)
        for(int i = 0; i < numOfBits - 1; ++i) {
            if(code[i] == 1) {
                code[i] = 0;
            }
            else {
                code[i] = 1;
            }
        }
        // 求补码并统计(补码等于反码加1)
        bool flag = true;   // 进位标志
        for(int i = 0; i < numOfBits - 1; ++i) {
            if(flag) {
                code[i] += 1;
                if(code[i] >= 2) {
                    flag = true;
                    code[i] = code[i] % 2;
                }
                else {
                    flag = false;
                }
            }
            
            if(code[i] == 1) {
                ++result;
            }
        }
        // 算上符号位
        ++result;
    }
    
    return result;;
}

int main(int argc, const char * argv[]) {
    std::cout << NumberOf1(3) << std::endl;     // 2
    std::cout << NumberOf1(1024) << std::endl;  // 1
    std::cout << NumberOf1(-3) << std::endl;    // 31
    std::cout << NumberOf1(-1) << std::endl;    // 32
    return 0;
}

数值的整数次方

  • 题目描述:实现函数 double Power(double base, int exponent),求base的exponent次方。【时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)
  • 解题思路:简单题。需明白0的任意次方是0、任意数的0次方都是1等就行了。
  • 源代码
#include 

double Power(double base, int exponent) {
    // 0的任意次方是0
    if(base == 0) {
        return 0;
    }
    // 任意数的0次方都是1
    if(exponent == 0) {
        return 1;
    }
    
    double result = 1;

    // 指数为正
    if(exponent > 0) {
        while(exponent--) {
            result *= base;
        }
    }
    // 指数为负
    else {
        while(exponent++) {
            result /= base;
        }
    }
    
    return result;
}

int main(int argc, const char * argv[]) {
    std::cout << Power(2.00000, 3) << std::endl;    // 8
    std::cout << Power(-9.00000, 3) << std::endl;   // -729
    std::cout << Power(-1.10000, 2) << std::endl;   // 1.21
    std::cout << Power(-2.00000, -2) << std::endl;   // 0.25
    return 0;
}

打印从1到最大的n位数

  • 题目描述:输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
  • 解题思路:简单题。注意使用 p o w pow pow函数需包含 < m a t h . h > <math.h>
  • 源代码
#include 
#include 
#include 
using namespace std;

vector<int> printNumbers(int n) {
    vector<int> result;
    for(int i = 1; i < pow(10, n); ++i) {
        result.push_back(i);
    }
    return result;
}

int main(int argc, const char * argv[]) {
    vector<int> result = printNumbers(3);
    return 0;
}

删除链表节点

  • 题目描述:给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。
  • 解题思路:简单题。注意特别判断头节点就好了。
  • 源代码
#include 
using namespace std;

struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
 };
 

ListNode* deleteNode(ListNode* head, int val) {
    // 如果删除的是首节点,直接返回第二个节点就够了
    if(val == head->val) {
        return head->next;
    }
    
    // 寻找被删除节点并删除(虚假的删除)
    ListNode* pre = head;
    ListNode* now = head->next;
    while(val != now->val) {
        pre = now;
        now = now->next;
    }
    pre->next = now->next;
    
    return head;
}

int main(int argc, const char * argv[]) {
    ListNode* a = new ListNode(2);
    ListNode* b = new ListNode(5);
    ListNode* c = new ListNode(1);
    ListNode* d = new ListNode(9);
    a->next = b;
    b->next = c;
    c->next = d;
    
    deleteNode(a, 1);
    
    cout << a->val << ", " << a->next->val << ", " << a->next->next->val << endl;
    
    delete a;
    delete b;
    delete c;
    delete d;
    return 0;
}

正则表达式匹配

  • 题目描述
    请实现一个函数用来匹配包括'.''*'的正则表达式。
    1.模式中的字符'.'表示任意一个字符
    2.模式中的字符'*'表示它前面的字符可以出现任意次(包含0次)。
    在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a""ab*ac*a"匹配,但是与"aa.a""ab*a"均不匹配。
    可能出现连续的'.',但没有连续的'*'
  • 解题思路

题目其实并不难,难点在于如何编程实现,思路一定要清晰。还是可以使用深度优先搜索来解决这个问题。

三个退出条件

  1. 如果二者中都为空说明匹配成功;
  2. 如果模式为空而字符不为空说明匹配失败;
  3. 如果字符为空而模式不为空,则需要判断是否模式中剩余的符号都是'*'

三种匹配状况

  1. '*':这种情况必须先处理,因为'*'前面会跟着字符或者'.',如果不先处理的话会与其他情况混淆。这种情况又分为'*'前面跟着字符或者'.'
  2. '.':这种情况最简单——跳过当前字符就可以了。
  3. 字符:这种情况也比较简单,直接比较即可。

建议跟着我的代码/注释捋一捋思路。

  • 源代码
#include 
using namespace std;

// 深度优先搜索
bool match(string str, string pattern) {
    // 三个退出条件
    // 如果二者中都为空说明匹配成功
    if(str.empty() && pattern.empty()) {
        return true;
    }
    // 如果模式为空而字符不为空说明匹配失败
    if(!str.empty() && pattern.empty()) {
        return false;
    }
    // 如果字符为空而模式不为空,则需要判断是否出现'*'
    if(str.empty() && !pattern.empty()) {
        for(int i = 0; i < pattern.length(); ++i) {
            if(i + 1 < pattern.length()) {
                if(pattern[i + 1] == '*') {
                    ++i;
                }
                else {
                    return false;
                }
            }
            else {
                return false;
            }
        }
        // 剩余模式均为'*'则匹配成功
        return true;
    }
    
    // 开始匹配(注意,一定要先处理'*')
    // 检测'*'
    if(pattern.length() > 1 && pattern[1] == '*') {
        string newPattern(pattern.begin() + 2, pattern.end());
        // 处理'.*'
        if(pattern[0] == '.') {
            // 先处理字符出现0次
            if(match(str, newPattern)) {
                return true;
            }
            // 处理字符出现1~n次
            for(int i = 0; i < str.length(); ++i) {
                string newStr(str.begin() + i + 1, str.end());
                if(match(newStr, newPattern)) {
                    return true;
                }
            }
        }
        // 处理字符和'*'
        else {
            // 先处理字符出现0次
            if(match(str, newPattern)) {
                return true;
            }
            // 处理字符出现1~n次
            for(int i = 0; i < str.length(); ++i) {
                if(str[i] == pattern[0]) {
                    string newStr(str.begin() + i + 1, str.end());
                    if(match(newStr, newPattern)) {
                        return true;
                    }
                }
                else {
                    break;
                }
            }
        }
    }
    // 检测'.'
    else if(pattern[0] == '.') {
        string newStr(str.begin() + 1, str.end());
        string newPattern(pattern.begin() + 1, pattern.end());
        return match(newStr, newPattern);
    }
    // 普通匹配
    else {
        if(str[0] == pattern[0]) {
            string newStr(str.begin() + 1, str.end());
            string newPattern(pattern.begin() + 1, pattern.end());
            return match(newStr, newPattern);
        }
        else {
            return false;
        }
    }
    
    return false;
}

int main(int argc, const char * argv[]) {
    cout << match("aaab", "aaab") << endl;      // 1
    cout << match("a.a", "aaaa") << endl;       // 0
    cout << match("a.a", "aza") << endl;        // 1
    cout << match("aaa", "aaaz*") << endl;      // 1
    
    cout << match("aaa", "a*a") << endl;        // 1
    cout << match("aad", "c*a*d") << endl;      // 1
    cout << match("aaab", "a*a*a*c") << endl;   // 0
    cout << match("a", ".") << endl;             // 1
    
    return 0;
}

表示数值的字符串

  • 题目描述

请实现一个函数用来判断字符串str是否表示数值(包括科学计数法的数字,小数和整数)。【时间复杂度和空间复杂度都是 O ( n ) O(n) O(n)

科学计数法的数字(按顺序)可以分成以下几个部分:
1.若干空格
2.一个整数或者小数
3.(可选)一个 ‘e’ 或 ‘E’ ,后面跟着一个整数(可正可负)
4.若干空格

小数(按顺序)可以分成以下几个部分:
1.若干空格
2.(可选)一个符号字符(‘+’ 或 ‘-’)
3. 可能是以下描述格式之一:
3.1 至少一位数字,后面跟着一个点 ‘.’
3.2 至少一位数字,后面跟着一个点 ‘.’ ,后面再跟着至少一位数字
3.3 一个点 ‘.’ ,后面跟着至少一位数字
4.若干空格

整数(按顺序)可以分成以下几个部分:
1.若干空格
2.(可选)一个符号字符(‘+’ 或 ‘-’)
3. 至少一位数字
4.若干空格

  • 解题思路

简单点的话,就是分别判断是否是三种类型,最后用或运算将结果整合起来就好了。

但是看到这个时空复杂度,就应该知道不能分成三种类型去判断,而是在一个循环中就要判断出来了。应该注意到科学技术法其实就是“小数/整数” + “E/e” + “整数”,因此题目可以简化为判断是否为小数或者整数

思路比较难以描述,总的来说就是分类讨论:如果监测到非法输入(例如“E/e”后面出现小数点),就直接返回false;如果函数能走到最后,则返回true。建议看代码/注释理解吧。

不过,说实话分类讨论还是比较难的,我最开始提交的时候通过率志勇 31 / 39 31/39 31/39,后面调试了六七次…

  • 源代码
#include 
using namespace std;

bool isNumeric(string str) {
    // 消除前面空格
    while(!str.empty() && str[0] == ' ') {
        str.erase(str.begin());
    }
    
    // 判断非法输入
    if(str.empty()) {
        return false;
    }
    
    // 中间变量
    bool number = false;    // 数字是否已出现过
    bool dot = false;   // 小数点是否已出现过
    bool plus_minus = false;    // 加减号是否出现过
    bool E = false;     // E和e是否出现过
    
    // 开始判断
    for(int i = 0; i < str.length(); ++i) {
        // 读取到空格
        if(str[i] == ' ') {
            // 空格只能出现在最后面,否则什么都不是
            while(++i < str.length()) {
                if(str[i] != ' ') {
                    return false;
                }
            }
        }
        // 读取到数字
        else if(str[i] - '0' >= 0 && '9' - str[i] >= 0) {
            // 数字可以出现在任何地方,因此无需处理
            number = true;
        }
        // 读取到E/e
        else if(str[i] == 'E' || str[i] == 'e') {
            // E/e只能有一个
            if(E) {
                return false;
            }
            // E/e前面必须是小数或者整数,即必须出现数字
            if(!number) {
                return false;
            }
            // E/e后面必须有东西且不是空格
            if(i == str.length() - 1 || str[i + 1] == ' ') {
                return false;
            }
            // 后面只能存在整数
            // 加减号、数字、小数点符号应先置0
            plus_minus = false;
            number = false;
            dot = false;
            E = true;
        }
        // 读取到加减号
        else if(str[i] == '-' || str[i] == '+') {
            // 加减号不能重复出现,前面也不能有小数点,也不能有数字
            if(plus_minus || dot || number) {
                return false;
            }
            // 加减号后面必须有东西且不是空格
            if(i == str.length() - 1 || str[i + 1] == ' ') {
                return false;
            }
            plus_minus = true;
        }
        // 读取到小数点
        else if(str[i] == '.') {
            // 小数点前面不能有小数点
            if(dot) {
                return false;
            }
            // E/e后面只能是整数,因此不能出现小数点
            if(E) {
                return false;
            }
            // 小数点前面或者后面需要有数字
            if(!number) {
                if(i == str.length() - 1 || (str[i + 1] - '0' < 0 && '9' - str[i + 1] < 0)) {
                    return false;
                }
            }
            dot = true;
        }
        // 检测到非法输入
        else {
            return false;
        }
        
    }
    
    return true;
}

int main(int argc, const char * argv[]) {
    cout << (isNumeric("    123.45e+6") == true) << endl;
    cout << (isNumeric("+100") == true) << endl;
    cout << (isNumeric("5e2") == true) << endl;
    cout << (isNumeric("") == false) << endl;
    cout << (isNumeric("-123") == true) << endl;
    cout << (isNumeric("3.1416") == true) << endl;
    cout << (isNumeric("-1E-16") == true) << endl;
    cout << (isNumeric("12e") == false) << endl;
    cout << (isNumeric("1a3.14") == false) << endl;
    cout << (isNumeric("1.2.3") == false) << endl;
    cout << (isNumeric("+-5") == false) << endl;
    cout << (isNumeric("12e+4.3") == false) << endl;
    cout << (isNumeric(".") == false) << endl;
    cout << (isNumeric("    .2") == true) << endl;
    cout << (isNumeric("+   a") == false) << endl;
    cout << (isNumeric(" 12e    ") == false) << endl;
    cout << (isNumeric(" 12    ") == true) << endl;
    return 0;
}

调整数组顺序使奇数位于偶数前面(一)

  • 题目描述:输入一个长度为 n n n 整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前面部分,所有的偶数位于数组的后面部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
  • 解题思路:很简单,遍历一次数组,把奇数按顺序存到奇数数组、把偶数按顺序存到偶数数组,最后用insert函数拼接在一起就可以了。时间、空间复杂度都是 O ( n ) O(n) O(n)
  • 源代码
#include 
#include 
using namespace std;

vector<int> reOrderArray(vector<int>& array) {
    vector<int> odd, even, result;
    for(int i = 0; i < array.size(); ++i) {
        if(array[i] % 2 == 1) {
            odd.push_back(array[i]);
        }
        else {
            even.push_back(array[i]);
        }
    }
    result.insert(result.end(), odd.begin(), odd.end());
    result.insert(result.end(), even.begin(), even.end());
    return result;
}

int main(int argc, const char * argv[]) {
    vector<int> array1{1, 2, 3, 4};
    vector<int> array2{2, 4, 6, 5, 7};
    vector<int> array3{1, 3, 5, 6, 7};
    array1 = reOrderArray(array1);
    array2 = reOrderArray(array2);
    array3 = reOrderArray(array3);
    return 0;
}

链表中倒数最后k个结点

  • 题目描述:输入一个长度为 n n n 的链表,设链表中的元素的值为 a i a_i ai ,返回该链表中倒数第 k k k 个节点。如果该链表长度小于 k k k,请返回一个长度为 0 0 0 的链表。
  • 解题思路:很简单,先把链表指针移到正数第 k k k 个节点,这样倒数第 k k k个节点就是头节点,顺便判断链表长度是不是小于 k k k。之后在遍历链表的时候,两个下标同时向后移动直到最后一个元素就行了。
  • 源代码
#include 


struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

ListNode* FindKthToTail(ListNode* pHead, int k) {
    // 检查非法输入
    if(pHead == nullptr || k == 0) {
        return nullptr;
    }
    
    ListNode* result = pHead;
    for(int i = 0; i < k - 1; ++i) {
        if(pHead->next != nullptr) {
            pHead = pHead->next;
        }
        else {
            return nullptr;
        }
    }
    while(pHead->next != nullptr) {
        result = result->next;
        pHead = pHead->next;
    }
    return result;
}

int main(int argc, const char * argv[]) {
    ListNode* a = new ListNode(1);
    ListNode* b = new ListNode(2);
    ListNode* c = new ListNode(3);
    ListNode* d = new ListNode(4);
    ListNode* e = new ListNode(5);
    a->next = b;
    b->next = c;
    c->next = d;
    d->next = e;
    
    std::cout << FindKthToTail(a, 2)->val << std::endl;
    
    return 0;
}

链表中环的入口

  • 题目描述:给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。
  • 解题思路:维护一个等长数组,记录每个节点是否被访问过,如果访问到一个节点已经被访问过了,那么这个节点就是环的入口。这个解法的时间复杂度和空间复杂度都是 O ( n ) O(n) O(n)。但是想不到时间复杂度是 O ( n ) O(n) O(n)、空间复杂度是 O ( 1 ) O(1) O(1)的解法。
  • 源代码
#include 

struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

ListNode* EntryNodeOfLoop(ListNode* pHead) {
    // 检查非法输入
    if(pHead == nullptr) {
        return nullptr;
    }
    
    const int MAX_VALUE = 10000;
    int* visited = new int[MAX_VALUE + 1];
    memset(visited, 0, (MAX_VALUE + 1)*sizeof(int));
    
    while(pHead->next != nullptr) {
        if(visited[pHead->val] == 1) {
            return pHead;
        }
        else {
            visited[pHead->val] = 1;
        }
        pHead = pHead->next;
    }
    
    return nullptr;
}

int main(int argc, const char * argv[]) {
    ListNode* a = new ListNode(1);
    ListNode* b = new ListNode(2);
    ListNode* c = new ListNode(3);
    ListNode* d = new ListNode(4);
    ListNode* e = new ListNode(5);
    a->next = b;
    b->next = c;
    c->next = d;
    d->next = e;
    e->next = c;
    std::cout << EntryNodeOfLoop(a)->val << std::endl;
    return 0;
}

反转链表

  • 题目描述:给定一个单链表的头结点 p H e a d pHead pHead(该头节点是有值的,比如在下图,它的 v a l val val 1 1 1),长度为 n n n,反转该链表后,返回新链表的表头。
  • 解题思路:简单题。获取到下一个节点后,就把当前节点的next指向前一个。
  • 源代码
#include 

struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

ListNode* revertList(ListNode* pHead) {
    // 检查非法输入
    if(pHead == nullptr) {
        return nullptr;
    }
    
    // 如果只有一个节点的话直接返回就可以了
    if(pHead->next == nullptr) {
        return pHead;
    }
    
    ListNode* now = pHead->next;
    ListNode* pre = pHead;
    ListNode* next;
    pHead->next = nullptr;
    while(now != nullptr) {
        next = now->next;
        now->next = pre;
        pre = now;
        now = next;
    }
    
    return pre;
}

int main(int argc, const char * argv[]) {
    ListNode* a = new ListNode(1);
    ListNode* b = new ListNode(2);
    ListNode* c = new ListNode(3);
    ListNode* d = new ListNode(4);
    ListNode* e = new ListNode(5);
    a->next = b;
    b->next = c;
    c->next = d;
    d->next = e;
    std::cout << revertList(a)->val << std::endl;
    return 0;
}

合并两个排序的链表

  • 题目描述:输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
  • 解题思路:虽然是链表,但本质上还是归并排序。
  • 源代码
#include 

struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

ListNode* merge(ListNode* pHead1, ListNode* pHead2) {
    // 检查非法输入
    if(pHead1 == nullptr && pHead2 == nullptr) {
        return nullptr;
    }
    
    // 初始化中间变量
    ListNode* pHead;
    ListNode* p;
    ListNode* p1 = pHead1;
    ListNode* p2 = pHead2;
    
    // 确定头节点
    if(pHead1 == nullptr) {
        pHead = p2;
        p2 = p2->next;
    }
    else if(pHead2 == nullptr){
        pHead = p1;
        p1 = p1->next;
    }
    else {
        if(pHead1->val <= pHead2->val) {
            pHead = p1;
            p1 = p1->next;
        }
        else {
            pHead = p2;
            p2 = p2->next;
        }
    }
    p = pHead;
    
    while(p1 != nullptr && p2 != nullptr) {
        if(p1->val <= p2->val) {
            p->next = p1;
            p1 = p1->next;
        }
        else {
            p->next = p2;
            p2 = p2->next;
        }
        p = p->next;
    }
    if(p1 != nullptr) {
        p->next = p1;
        p1 = p1->next;
        p = p->next;
    }
    if(p2 != nullptr) {
        p->next = p2;
        p2 = p2->next;
        p = p->next;
    }
    
    return pHead;
}

int main(int argc, const char * argv[]) {
    ListNode* a = new ListNode(1);
    ListNode* b = new ListNode(5);
    ListNode* c = new ListNode(6);
    ListNode* d = new ListNode(8);
    ListNode* e = new ListNode(10);
    a->next = b;
    b->next = c;
    c->next = d;
    d->next = e;
    ListNode* f = new ListNode(1);
    ListNode* g = new ListNode(3);
    ListNode* h = new ListNode(4);
    ListNode* i = new ListNode(8);
    ListNode* j = new ListNode(9);
    f->next = g;
    g->next = h;
    h->next = i;
    i->next = j;
    ListNode* result1 = merge(a, f);
    ListNode* result2 = merge(a, nullptr);
    return 0;
}

树的子结构

  • 题目描述:输入两棵二叉树A,B,判断B是不是A的子结构。(我们约定空树不是任意一个树的子结构)。假如给定A为{8,8,7,9,2,#,#,#,#,4,7},B为{8,9,2},2个树的结构如下,可以看出B是A的子结构。
  • 解题思路:依然是深度优先搜索,但是要注意退出条件——叶节点值相同。
  • 源代码
#include 
#include 
using namespace std;

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

// 递归的深度优先搜索,目的是判断两棵树是否一致
bool areSame(TreeNode* pRoot1, TreeNode* pRoot2) {
    // 已经完全匹配上了
    if(pRoot2 == nullptr) {
        return true;
    }
    // 失败条件
    if(pRoot1 == nullptr) {
        return false;
    }
    
    // 判断
    if(pRoot1->val == pRoot2->val) {
        return areSame(pRoot1->left, pRoot2->left) && areSame(pRoot1->right, pRoot2->right);
    }
    else {
        return false;
    }
}


// 主搜索函数
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
    // 判断非法输入
    if(pRoot1 == nullptr || pRoot2 == nullptr) {
        return false;
    }
    
    // 遍历待查找树
    stack<TreeNode*> s;
    s.push(pRoot1);
    while(!s.empty()) {
        // 出入栈以遍历树
        TreeNode* now = s.top();
        s.pop();
        if(now->left != nullptr) {
            s.push(now->left);
        }
        if(now->right != nullptr) {
            s.push(now->right);
        }
        
        // 判断是否相同
        if(areSame(now, pRoot2)) {
            return true;
        }
    }
    
    return false;
}

int main(int argc, const char * argv[]) {
    TreeNode* a = new TreeNode(8);
    TreeNode* b = new TreeNode(8);
    TreeNode* c = new TreeNode(7);
    TreeNode* d = new TreeNode(9);
    TreeNode* e = new TreeNode(2);
    TreeNode* f = new TreeNode(4);
    TreeNode* g = new TreeNode(7);
    a->left = b;
    a->right = c;
    b->left = d;
    b->right = e;
    e->left = f;
    e->right = g;
    
    TreeNode* A = new TreeNode(8);
    TreeNode* B = new TreeNode(9);
    TreeNode* C = new TreeNode(2);
    A->left = B;
    A->right = C;
    
    TreeNode* E = new TreeNode(8);
    TreeNode* F = new TreeNode(10);
    TreeNode* G = new TreeNode(2);
    E->left = F;
    E->right = G;
    
    TreeNode* H = new TreeNode(9);
    TreeNode* I = new TreeNode(2);
    H->left = I;
    
    cout << (HasSubtree(a, A) == true) << endl;
    cout << (HasSubtree(a, E) == false) << endl;
    cout << (HasSubtree(a, H) == false) << endl;

    return 0;
}

二叉树的镜像

  • 题目描述:操作给定的二叉树,将其变换为源二叉树的镜像。
  • 解题思路:简单题,遍历树,把每个节点的左右子树交换就好了。
  • 源代码
#include 
#include 
using namespace std;

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

// 主搜索函数
TreeNode* Mirror(TreeNode* pRoot) {
    // 判断非法输入
    if(pRoot == nullptr) {
        return nullptr;
    }
    
    // 遍历树,交换每个左右子树
    stack<TreeNode*> s;
    s.push(pRoot);
    while(!s.empty()) {
        // 出入栈以遍历树
        TreeNode* now = s.top();
        s.pop();
        if(now->left != nullptr) {
            s.push(now->left);
        }
        if(now->right != nullptr) {
            s.push(now->right);
        }
        
        // 交换左右子树
        TreeNode* temp;
        temp = now->left;
        now->left = now->right;
        now->right = temp;
    }
    
    return pRoot;
}

int main(int argc, const char * argv[]) {
    TreeNode* a = new TreeNode(8);
    TreeNode* b = new TreeNode(8);
    TreeNode* c = new TreeNode(7);
    TreeNode* d = new TreeNode(9);
    TreeNode* e = new TreeNode(2);
    TreeNode* f = new TreeNode(4);
    TreeNode* g = new TreeNode(7);
    a->left = b;
    a->right = c;
    b->left = d;
    b->right = e;
    e->left = f;
    e->right = g;
    
    TreeNode* A = new TreeNode(8);
    TreeNode* B = new TreeNode(9);
    TreeNode* C = new TreeNode(2);
    A->left = B;
    A->right = C;
    
    TreeNode* E = new TreeNode(8);
    TreeNode* F = new TreeNode(10);
    TreeNode* G = new TreeNode(2);
    E->left = F;
    E->right = G;
    
    TreeNode* H = new TreeNode(9);
    TreeNode* I = new TreeNode(2);
    H->left = I;
    
    cout << Mirror(a)->val << endl;
    cout << Mirror(E)->val << endl;
    cout << Mirror(H)->val << endl;

    return 0;
}

对称的二叉树

  • 题目描述:给定一棵二叉树,判断其是否是自身的镜像(即:是否对称)(备注:就是对折是否一样)。
  • 解题思路:第一时间竟然没想出思路,但是想出之后觉得确实是简单题。判断的方法就是左边的节点的左子节点要右边的节点的右子节点。
  • 源代码
#include 
#include 
using namespace std;

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

bool isSymmetrical(TreeNode* pRoot) {
    // 对一些特殊输入的判断
    if(pRoot == nullptr) {
        return true;
    }
    if(pRoot->left == nullptr && pRoot->right == nullptr) {
        return true;
    }
    if(pRoot->left != nullptr && pRoot->right == nullptr) {
        return false;
    }
    if(pRoot->left == nullptr && pRoot->right != nullptr) {
        return false;
    }
    if(pRoot->left->val != pRoot->right->val) {
        return false;
    }
    
    // 开始判断
    stack<TreeNode*> ls;
    stack<TreeNode*> rs;
    ls.push(pRoot->left);
    rs.push(pRoot->right);
    while(!ls.empty() && !rs.empty()) {
        TreeNode* ln = ls.top();
        ls.pop();
        TreeNode* rn = rs.top();
        rs.pop();
        
        if(ln->left == nullptr) {
            if(rn->right == nullptr) {
                // 相等,但是无需添加节点了
            }
            else {
                // 不相等
                return false;
            }
        }
        else {
            if(rn->right == nullptr) {
                // 不相等
                return false;
            }
            else {
                if(ln->left->val == rn->right->val) {
                    // 相等,且要添加节点
                    ls.push(ln->left);
                    rs.push(rn->right);
                }
                else {
                    // 不相等
                    return false;
                }
            }
        }
        
        if(ln->right == nullptr) {
            if(rn->left == nullptr) {
                // 相等,但是无需添加节点了
            }
            else {
                // 不相等
                return false;
            }
        }
        else {
            if(rn->left == nullptr) {
                // 不相等
                return false;
            }
            else {
                if(ln->right->val == rn->left->val) {
                    // 相等,且要添加节点
                    ls.push(ln->right);
                    rs.push(rn->left);
                }
                else {
                    // 不相等
                    return false;
                }
            }
        }
    }
    
    if(ls.empty() && rs.empty()) {
        return true;
    }
    else {
        return false;
    }
}

int main(int argc, const char * argv[]) {
    TreeNode* a = new TreeNode(8);
    TreeNode* b = new TreeNode(8);
    TreeNode* c = new TreeNode(7);
    TreeNode* d = new TreeNode(9);
    TreeNode* e = new TreeNode(2);
    TreeNode* f = new TreeNode(4);
    TreeNode* g = new TreeNode(7);
    a->left = b;
    a->right = c;
    b->left = d;
    b->right = e;
    e->left = f;
    e->right = g;
    
    TreeNode* A = new TreeNode(8);
    TreeNode* B = new TreeNode(9);
    TreeNode* C = new TreeNode(9);
    A->left = B;
    A->right = C;
    
    TreeNode* E = new TreeNode(8);
    TreeNode* F = new TreeNode(10);
    TreeNode* G = new TreeNode(2);
    E->left = F;
    E->right = G;
    
    TreeNode* H = new TreeNode(9);
    TreeNode* I = new TreeNode(2);
    H->left = I;
    
    cout << isSymmetrical(a) << endl;
    cout << isSymmetrical(A) << endl;
    cout << isSymmetrical(E) << endl;
    cout << isSymmetrical(H) << endl;

    return 0;
}

顺时针打印矩阵

  • 题目描述:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
  • 解题思路:只需要用一点小技巧。用四个变量来限制四个边界,再用一个变量来记录当前前进的方向(右下左上的顺序变化)。
  • 源代码
#include 
#include 
using namespace std;

vector<int> printMatrix(vector<vector<int> > matrix) {
    // 用来存储结果
    vector<int> result;
    
    // 检测非法输入
    if(matrix.empty()) {
        return result;
    }
    
    // 设置边界
    int left = 0;
    int right = matrix[0].size() - 1;
    int bottom = 0;
    int top = matrix.size() - 1;
    
    // 设置方向(右-下-左-上)
    int dir = 0;
    
    // 计算总输出量
    int num = matrix.size() * matrix[0].size();
    
    // 开始输出
    int x = 0, y = 0;
    while(result.size() < num) {
        // 向右走
        if(dir == 0) {
            // 检测是否遇到边界
            if(y == right) {
                dir = (dir + 1) % 4;
                ++bottom;
            }
            else {
                result.push_back(matrix[x][y]);
                ++y;
            }
        }
        // 向下走
        else if(dir == 1){
            // 检测是否遇到边界
            if(x == top) {
                dir = (dir + 1) % 4;
                --right;
            }
            else {
                result.push_back(matrix[x][y]);
                ++x;
            }
        }
        // 向左走
        else if(dir == 2) {
            // 检测是否遇到边界
            if(y == left) {
                dir = (dir + 1) % 4;
                --top;
            }
            else {
                result.push_back(matrix[x][y]);
                --y;
            }
        }
        // 向上走
        else {
            // 检测是否遇到边界
            if(x == bottom) {
                dir = (dir + 1) % 4;
                ++left;
            }
            else {
                result.push_back(matrix[x][y]);
                --x;
            }
        }
    }
    
    return result;
}

int main(int argc, const char * argv[]) {
    vector<vector<int>> matrix1{{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
    printMatrix(matrix1);
    vector<vector<int>> matrix2{{1},{2},{3},{4}};
    printMatrix(matrix2);
    return 0;
}

暂时先刷到这了,以后有空再刷…

你可能感兴趣的:(算法编程题,c++,算法)