【乱七八糟的笔记】——前缀树
前缀树的构建
- 利用数组构建
// change this value to adapt to different cases
#define N 26struct TrieNode {TrieNode* children[N];// you might need some extra values according to different cases
};/** Usage:* Initialization: TrieNode root = new TrieNode();* Return a specific child node with char c: (root->children)[c - 'a']*/
class TrieNode{ // 前缀树节点的实现
public:TrieNode* children[26]; // 前缀树可以存储26个小写字母,存在next数组中。bool flage;TrieNode() { // 构造函数memset(children, NULL, sizeof(children)); // 分配空间flage = false;}~TrieNode() { // 析构函数。此处可以不写,实际中写上较好。for (int i = 0; i < 26; i++) {if(children[i]) delete children[i];}}
};
访问子节点更加快捷,且容易,但并非所有子节点都会用到,会造成空间的浪费。
- 利用哈希表构建
struct TrieNode {unordered_map<char, TrieNode*> children;// you might need some extra values according to different cases
};/** Usage:* Initialization: TrieNode root = new TrieNode();* Return a specific child node with char c: (root->children)[c]*/
访问子节点更加容易,但速度稍慢,但方法较为灵活,不会收到固定长度、范围的限制。
前缀树的插入
Initialize: cur = root
for each char c in target string S:if cur does not have a child c:cur.children[c] = new Trie nodecur = cur.children[c]
cur is the node which represents the string S
前缀树的搜索
Initialize: cur = root
for each char c in target string S:if cur does not have a child c:search failscur = cur.children[c]
search successes
前缀树的实现
struct TrieNode {bool flag;map<char, TrieNode*> next;
};
class Trie {
private:TrieNode* root;public:/** Initialize your data structure here. */Trie() {root = new TrieNode();}/** Inserts a word into the trie. */void insert(string word) {TrieNode* p = root;for (int i = 0; i < word.length(); ++i) {if ((p->next).count(word[i]) <= 0) {// insert a new node if the path does not exist(p->next).insert(make_pair(word[i], new TrieNode()));}p = (p->next)[word[i]];}p->flag = true;}/** Returns if the word is in the trie. */bool search(string word) {TrieNode* p = root;for (int i = 0; i < word.length(); ++i) {if ((p->next).count(word[i]) <= 0) {return false;}p = (p->next)[word[i]];}return p->flag;}/** Returns if there is any word in the trie that starts with the given prefix. */bool startsWith(string prefix) {TrieNode* p = root;for (int i = 0; i < prefix.length(); ++i) {if ((p->next).count(prefix[i]) <= 0) {return false;}p = (p->next)[prefix[i]];}return true;}
};/*** Your Trie object will be instantiated and called as such:* Trie obj = new Trie();* obj.insert(word);* bool param_2 = obj.search(word);* bool param_3 = obj.startsWith(prefix);*/
前缀和数组
注意的点 为了省去边界判断,前缀和数组
多开辟一个
从1到n
class PrefixSum {
private:// 前缀和数组vector<int> prefix;public:/* 输入一个数组,构造前缀和 */PrefixSum(vector<int> nums) {int n = nums.size();prefix.resize(n+1);// 计算 nums 的累加和for (int i = 1; i <= n; i++) {prefix[i] = prefix[i - 1] + nums[i - 1];}}/* 查询闭区间 [i, j] 的累加和 */int query(int i, int j) {return prefix[j + 1] - prefix[i];}
};
一维数组中的前缀和
303. 区域和检索 - 数组不可变
NumArray(int[] nums)
使用数组nums
初始化对象int sumRange(int i, int j)
返回数组nums
中索引left
和right
之间的元素的 总和 ,包含left
和right
两点(也就是nums[left] + nums[left + 1] + ... + nums[right]
)
输入:
["NumArray", "sumRange", "sumRange", "sumRange"]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1))
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))
代码
class NumArray {
public:vector<int> preSum;NumArray(vector<int>& nums) {preSum.resize(nums.size());int sum = 0;for(int i = 0; i<nums.size(); i++){sum+=nums[i];preSum[i] = sum;}}int sumRange(int left, int right) {return left == 0?preSum[right]:(preSum[right] - preSum[left-1]);}
};// 简单写法, 避免边界判断
class NumArray {
public:vector<int> preSum;NumArray(vector<int>& nums) {int n = nums.size();preSum.resize(n+1);for(int i = 1; i<n+1; i++){preSum[i] = preSum[i-1] + nums[i-1];}}int sumRange(int left, int right) {return preSum[right+1] - preSum[left];}
};
724. 寻找数组的中心下标
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0
,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1
。
输入:nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。
暴力超时
class Solution {
public:int pivotIndex(vector<int>& nums) {if( nums.size() == 1) return 0;if(nums.size() == 2|| nums.size() == 0) return -1;for(int i = 0; i< nums.size() ; i++){int sum1 = 0, sum2 = 0;for(int j =0; j<i;j++)sum1+=nums[j];for(int j = i+1;j<nums.size();j++)sum2+=nums[j];if(sum1 == sum2) return i;}return -1;}
};
前缀和
class Solution {
public:int pivotIndex(vector<int>& nums) {int n = nums.size();vector<int> preSum(n+1);int sum = accumulate(nums.begin(), nums.end(), 0);for(int i = 1; i<=n; i++){preSum[i] = preSum[i-1] + nums[i-1];int temp = sum - nums[i-1];if(temp %2 == 0 && temp /2 == preSum[i-1]){return i-1;}}return -1;}
};
1352. 最后 K 个数的乘积
请你实现一个「数字乘积类」ProductOfNumbers
,要求支持下述两种方法:
题目数据保证:任何时候,任一连续数字序列的乘积都在 32-bit 整数范围内,不会溢出。
输入:
["ProductOfNumbers","add","add","add","add","add","getProduct","getProduct","getProduct","add","getProduct"]
[[],[3],[0],[2],[5],[4],[2],[3],[4],[8],[2]]输出:
[null,null,null,null,null,null,20,40,0,null,32]
前缀积
主要是0的影响 有0的话重新开始前缀
class ProductOfNumbers {
public:vector<int> preMulti;int size;ProductOfNumbers() {preMulti.resize(40001);preMulti[0] = 1;size = 0;}void add(int num) {if(num == 0) size = 0;else{size++;preMulti[size] = num*preMulti[size-1];}}int getProduct(int k) {if(size < k) return 0;return preMulti[size] / preMulti[size-k];}
};
二维数组中的前缀和
304. 二维区域和检索 - 矩阵不可变
NumMatrix(int[][] matrix)
给定整数矩阵matrix
进行初始化int sumRegion(int row1, int col1, int row2, int col2)
返回 左上角(row1, col1)
、右下角(row2, col2)
所描述的子矩阵的元素 总和 。
输入:
["NumMatrix","sumRegion","sumRegion","sumRegion"]
[[[[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]],[2,1,4,3],[1,1,2,2],[1,2,2,4]]
输出:
[null, 8, 11, 12]解释:
NumMatrix numMatrix = new NumMatrix([[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]);
numMatrix.sumRegion(2, 1, 4, 3); // return 8 (红色矩形框的元素总和)
numMatrix.sumRegion(1, 1, 2, 2); // return 11 (绿色矩形框的元素总和)
numMatrix.sumRegion(1, 2, 2, 4); // return 12 (蓝色矩形框的元素总和)
代码
//笨比前缀和
class NumMatrix {
public:vector<vector<int>> preSum;NumMatrix(vector<vector<int>>& matrix) {int m = matrix.size();int n = matrix[0].size();for(int i = 0; i<m; i++){int sum = 0;vector<int> temp(n+1);for(int j = 1; j<n+1; j++){temp[j] = temp[j-1] + matrix[i][j-1];}//cout<<i<<endl;preSum.push_back(temp);}}int sumRegion(int row1, int col1, int row2, int col2) {int ans = 0;int minRow = min(row1, row2);int maxRow = max(row1, row2);int minCol = min(col1, col2);int maxCol = max(col1, col2);for(int i = minRow; i<= maxRow; i++){ans += preSum[i][maxCol+1] - preSum[i][minCol];}return ans;}
};//真正的二维前缀和数组
class NumMatrix {
public:// 定义:preSum[i][j] 记录 matrix 中子矩阵 [0, 0, i-1, j-1] 的元素和vector<vector<int>> preSum;NumMatrix(vector<vector<int>>& matrix) {int m = matrix.size();if(m == 0) return;int n = matrix[0].size();// 构造前缀和矩阵preSum.resize(m+1, vector<int>(n+1));for(int i = 1; i<=m; i++){for(int j = 1; j<=n; j++){// 计算每个矩阵 [0, 0, i, j] 的元素和preSum[i][j] = preSum[i-1][j] + preSum[i][j-1] - preSum[i-1][j-1] + matrix[i-1][j-1];}}}//速记 前缀和做减法的时候 永远是大的那边需要+1int sumRegion(int row1, int col1, int row2, int col2) {// 计算子矩阵 [x1, y1, x2, y2] 的元素和return preSum[row2 + 1][col2 + 1] - preSum[row1][col2+1] -preSum[row2+1][col1] + preSum[row1][col1];}
};
前缀和优化
560. 和为 K 的子数组
给你一个整数数组 nums
和一个整数 k
,请你统计并返回该数组中和为 k
的连续子数组的个数。
输入:nums = [1,1,1], k = 2
输出:2
输入:nums = [1,2,3], k = 3
输出:2
思路
补充修正:最后一条正确路径应该是3-4-5-6-7
个人理解本质上还是前缀和数组presum[i]与presum[j]的差值双重遍历的优化
重点在于哈希的初始化
比如说 从0到某个索引i的前缀和 就是k 也就是从头开始到i的连续子数组和presum[i]就是k,这个时候presum - k 就等于0了,提前把0放一个val = 1就可以统计这个情况了
当出现前缀和等于k的时候会把这段算到答案里,不然hashmap[0]默认为0,就会少一个解
比如1 2 1 target = 2 就不会少
而1 1 1 target = 2 会少一个 情况为前两个1
代码
//笨比的前缀和用法
class Solution {
public:int subarraySum(vector<int>& nums, int k) {int n = nums.size();vector<int> preSum(n+1);for(int i = 1; i<=n; i++){preSum[i] = preSum[i-1] + nums[i-1];}int ans = 0;for(int i = 0; i<=n; i++){for(int j = i+1; j<=n; j++){if(preSum[j] - preSum[i] == k){ans++;}}}return ans;}
};//遍历
class Solution {
public:int subarraySum(vector<int>& nums, int k) {int count = 0;for (int start = 0; start < nums.size(); ++start) {int sum = 0;for (int end = start; end >= 0; --end) {sum += nums[end];if (sum == k) {count++;}}}return count;}
};//前缀和的最优解
// 3 5 2 -2 4 1 k = 5
//0 3 8 10 8 12 13 -5的个数
class Solution {
public:int subarraySum(vector<int>& nums, int k) {int count = 0;unordered_map<int, int> mapp;mapp[0] = 1; //初始化int sum = 0; for (int i = 0; i < nums.size(); ++i) {sum+=nums[i];int cc = mapp[sum-k];count+=cc;mapp[sum]++;}return count;}
};
剑指 Offer II 011. 0 和 1 个数相同的子数组
给定一个二进制数组 nums
, 找到含有相同数量的 0
和 1
的最长连续子数组,并返回该子数组的长度。
输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。
输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量 0 和 1 的最长连续子数组。
思路
- 将数组中的0换成-1, 求和为0的最长子数组 转换成前缀和问题
- 注意!处理0位置
代码
class Solution {
public:int findMaxLength(vector<int>& nums) {int n = nums.size();//将数组中的0换成-1, 求和为0的最长子数组 转换成前缀和问题for(int& num : nums) //这样写一定要&if(num == 0) num = -1;unordered_map<int, int> mapp;mapp[0] = -1; //注意!处理0位置int sum = 0;int ans = 0;for(int i = 0; i < n; i++){sum += nums[i];if(mapp.count(sum)) //之前也有前缀和 == sumans = max(ans, i - mapp[sum]);else mapp[sum] = i;}return ans;}
};
差分数组
差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减。
// 差分数组工具类
class Difference {
private:// 差分数组vector<int> diff;
public:/* 输入一个初始数组,区间操作将在这个数组上进行 */Difference(vector<int> nums) {int n = nums.size();diff.resize(n);;// 根据初始数组构造差分数组diff[0] = nums[0];for (int i = 1; i < n; i++) {diff[i] = nums[i] - nums[i - 1];}}/* 给闭区间 [i,j] 增加 val(可以是负数)*/void increment(int i, int j, int val) {diff[i] += val;if (j + 1 < diff.size()) {diff[j + 1] -= val;}}/* 返回结果数组 */vector<int> result() {vector<int> res(diff.size());// 根据差分数组构造结果数组res[0] = diff[0];for (int i = 1; i < diff.size(); i++) res[i] = res[i - 1] + diff[i];return res;}
};
370. 区间加法
可以直接用刚才的套路解决
vector<int> getModifiedArray(int length, vector<vector<int>> updates) {// nums 初始化为全 0vector<int> nums(length);// 构造差分解法vector<int> diff(length);//因为初始全为0 所以不需要如下初始化//diff[0] = nums[0];//for(int i = 1; i<length; i++){// diff[i] = nums[i] - nums[i-1];//}for (auto update : updates) {int i = update[0];int j = update[1];int val = update[2];diff[i]+=val;if(j+1<length)diff[j+1]-=val;}vector<int> ans;for(int i = 1; i<length; i++){ans[i] = ans[i-1] + diff[i];}return df.result();
}
1109. 航班预订统计
请你返回一个长度为 n
的数组 answer
,里面的元素是每个航班预定的座位总数。
输入:bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5
输出:[10,55,45,25,25]
解释:
航班编号 1 2 3 4 5
预订记录 1 : 10 10
预订记录 2 : 20 20
预订记录 3 : 25 25 25 25
总座位数: 10 55 45 25 25
因此,answer = [10,55,45,25,25]
class Solution {
public:vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {vector<int> ans(n);vector<int> origin(n);vector<int> diff(n);//diff不需要初始化了;for(int i = 0; i<bookings.size(); i++){int left = bookings[i][0]-1; //nmd坑老子int right = bookings[i][1]-1;int val = bookings[i][2];//操作diff[left] += val;if(right + 1 <n){diff[right+1]-=val;} }// for(int i = 0; i<n; i++)// cout<<diff[i]<<endl;ans[0] = diff[0];for(int i = 1; i<n; i++){ans[i] = ans[i-1] + diff[i];}return ans;}
};
1094. 拼车
假设你是一位顺风车司机,车上最初有 capacity
个空座位可以用来载客。由于道路的限制,车 只能 向一个方向行驶(也就是说,不允许掉头或改变方向,你可以将其想象为一个向量)。
这儿有一份乘客行程计划表 trips[][]
,其中 trips[i] = [num_passengers, start_location, end_location]
包含了第 i
组乘客的行程信息:
这些给出的地点位置是从你的 初始 出发位置向前行驶到这些地点所需的距离(它们一定在你的行驶方向上)。
请你根据给出的行程计划表和车子的座位数,来判断你的车是否可以顺利完成接送所有乘客的任务(当且仅当你可以在所有给定的行程中接送所有乘客时,返回 true
,否则请返回 false
)。
输入:trips = [[2,1,5],[3,3,7]], capacity = 4
输出:false
输入:trips = [[2,1,5],[3,3,7]], capacity = 5
输出:true
class Solution {
public:bool carPooling(vector<vector<int>>& trips, int capacity) {vector<int> diff(1001);//diff不用初始化for(int i = 0; i<trips.size(); i++){int left = trips[i][1];int right = trips[i][2] - 1;int val = trips[i][0];//jiajiajiadiff[left]+=val;if(right + 1<1001)diff[right+1] -= val;}vector<int> res(1001);res[0] = diff[0];if(res[0]>capacity) return 0; //不要忘记for(int i = 1; i<1001; i++){res[i] = res[i-1] + diff[i];if(res[i]>capacity)return 0;}return 1;}
};
【乱七八糟的笔记】——前缀树相关推荐
- 【LeetCode笔记】208. 实现Trie(前缀树)(Java、前缀树)
文章目录 题目描述 思路 & 代码 更新版 题目描述 属于那种,敲过一遍就不会忘了的那种题,就是学一个新的数据结构= = 二十六叉树! 思路 & 代码 isEnd 非常重要噢,只有正式 ...
- Trie(前缀树/字典树)及其应用
from:https://www.cnblogs.com/justinh/p/7716421.html Trie,又经常叫前缀树,字典树等等.它有很多变种,如后缀树,Radix Tree/Trie,P ...
- 数据库索引数据结构总结——ART树就是前缀树
数据库索引数据结构总结 from:https://zhewuzhou.github.io/2018/10/18/Database-Indexes/ 摘要 数据库索引是数据库中最重要的组成部分,而索引的 ...
- 有关完全二叉树求节点数和前缀树求字符串是否重复的两道算法题
1.给定一棵完全二叉树的头节点head,求其中的节点个数 递归的方法,时间复杂度为O((logN)²) 首先递归出头节点的左子树的最大深度H,之后再递归头节点的右子树的深度是否等于H,若相等则表示,头 ...
- 字典数(前缀树)的实现
[题目] 字典树又称为前缀树或者Trie树,是处理字符串常用的数据结构.假设组成所有单词的字符仅是'a'-'z',请实现字典树的结构,并包含以下四个主要的功能. void insert(String ...
- AC解 - Phone List(HDOJ#1671) 前缀树的一个应用
原题:http://acm.hdu.edu.cn/showproblem.php?pid=1671 Time Limit: 3000/1000 MS (Java/Others) Memory L ...
- 算法练习day13——190401(前缀树、贪心策略拼接字符串使字典序最小)
1.前缀树(Trie Tree) 1.1 字符串生成前缀树的过程 字母是填在路上的,不是填在节点上的. 首先是一个空的头结点: 加入"abc"到这棵树中: 头结点有到a的路吗?没有 ...
- Seq2Seq+前缀树:检索任务新范式(以KgCLUE为例)
©PaperWeekly 原创 · 作者 |苏剑林 单位 |追一科技 研究方向 |NLP.神经网络 两年前,在<万能的seq2seq:基于seq2seq的阅读理解问答>和<" ...
- Java 实现 Trie (前缀树)
LeetCode:https://leetcode-cn.com/problems/implement-trie-prefix-tree/ 什么是前缀树 Trie(发音类似 "try&quo ...
最新文章
- c 远程编辑linux文件,makefile - 在远程Linux机器上编译C ++ - “检测到时钟偏差”警告...
- (C++)用指针实现两数交换函数swap()的两种方法
- (转)!注意:PreTranslateMessage弹出框出错
- Photoshop一些人像处理技巧总结
- SQL--Chapter8--Working with Triggers and Transactions
- web 前端 如何分享到instagram_好程序员web前端教程分享前端javascript练习题三
- python中pi怎么使用_Python中使用Pi的对象传输
- 微信公众号发送客服消息【文本、图片】
- python权限不够cmd安装不了_python环境配置+matplotlib
- Python实现的爬取百度文
- 【Python】从0开始写爬虫——扒一下狗东
- linux 命令总结之tr命令
- Windows10解决耳机被识别为扬声器问题
- 自考行政管理计算机应用基础好考吗,通过自考《计算机应用基础》之经验谈
- 团体标准的意义,办理团体标准的好处
- 【SSH进阶之路】Hibernate基本映射(三)
- nodejs npm报错 重装 解决方法
- linux gdb中c(continue)的使用总结
- 7-6 计算存款利息
- 【Python数据分析 :Task4】