## leetcode 894. All Possible Full Binary Trees

2020-03-27 algorithm

# 题目：894. 所有可能的满二叉树

A full binary tree is a binary tree where each node has exactly 0 or 2 children.
Return a list of all possible full binary trees with N nodes. Each element of the answer is the root node of one possible tree.
Each node of each tree in the answer must have node.val = 0.
You may return the final list of trees in any order.

# 我的答案

``````/**
* Definition for a binary tree node.
* public class TreeNode {
*     int val;
*     TreeNode left;
*     TreeNode right;
*     TreeNode(int x) { val = x; }
* }
*/
class Solution {
/**
* 给定节点数量，返回所有可能的树的集合（每棵树即节点的排兵布阵）
*
* @param N 当前树所有节点数
* @return 返回树的根节点列表
*/
public List<TreeNode> allPossibleFBT(int nodeCnt) {
List<TreeNode> treeNodeList = new ArrayList<>();
// 如果是偶数返回空，如果是1返回1个节点的列表
/*
* 1. if N = 3 , the number of nodes combination are as follows
*       left    root    right
*        1       1        1
* --------------N = 3, res = 1----------
* 2. if N = 5 , the number of nodes combination are as follows
*       left    root    right
*        1       1        3 (recursion)
*        3       1        1
*   --------------N = 5, res = 1 + 1 = 2----------
* 3. if N = 7 , the number of nodes combination are as follows
*       left    root    right
*        1       1        5 (recursion)
*        3       1        3
*        5       1        1
*   --------------N = 7, res = 2 + 1 + 2 = 5----------
* 4. in order to make full binary tree, the node number must increase by 2
*/
//边界条件2:如果输入为1，那么结果就只有一个值为0的结点
if(nodeCnt <=0 || (nodeCnt&1) == 0){
return treeNodeList;
}else if((nodeCnt|1) == 1){
treeNodeList.add(new TreeNode(0));
return treeNodeList;
}
//我们知道一共有N个结点，root占了1个结点，左子树可能有1，3，5，..，N-2个结点
//对应的，右子树可能有N-2，..，5，3，1个结点
//那么，我们可以用一个循环，找到所有可能的左右子树的可能的数量的情况，把root放进列表里
for (int i = 1; i <= N-2; i += 2) {
//这里就是递归的精髓了，每次看到递归，就一头雾水
//在这里，我们不用去关心左右子树是怎么递归形成的
//我们可以仅仅去关心，这个函数，它实现的是什么功能
//allPossibleFBT(i)返回了一个列表，它存放着当结点数为i时，所有满足条件的树的root的集合
//我们可以认为，allPossibleFBT(i)存放着所有满足条件的左子树的集合
//同样，allPossibleFBT(N-1-i)存放着所有满足条件的右子树的集合
//这是由static List<TreeNode> allPossibleFBT(int N)这个函数的定义所确定的
List<TreeNode> leftSubTrees = allPossibleFBT(i);
List<TreeNode> rightSubTrees = allPossibleFBT(N - i - 1);

//接下来，就是左右子树的排列组合，当左子树为m时，右子树可能有right.size()个可能
//所以一共有right.size() * left.size()种可能
//我们把每一种排列，都放到我们所要的结果中
for (TreeNode l : leftSubTrees) {
for (TreeNode r : rightSubTrees) {
TreeNode root = new TreeNode(0);
root.left = l;
root.right = r;
//对于左子树有i个结点，右子树有N-1-i个结点时，我们把所有可能的树add到列表里
treeNodeList.add(root);
}
}
}
return treeNodeList;
}
}
``````

# 我的分析

1. 宏观地分析递归函数的功能作用给定节点数量，返回所有可能的树的集合（每棵树即节点的排兵布阵）
2. 状态转移方程 ：原问题可以分解为，对于节点总数为7的，它的leftChildrenTree节点总数为1,3,5 (给定节点数量，返回所有可能的树的集合),它的rightChildrenTree节点总数为5,3,1 (给定节点数量，返回所有可能的树的集合)，然后算它们的笛卡尔积，对于每种左右子树的排兵布阵，均新建一个根节点构建一颗新的树，并加到开头的treeNodeList里面
3. 退出条件 ：分解到最后，当最小粒度子问题入参为1的时候，直接构建单个节点的树立即return

## leetcode 315. 计算右侧小于当前元素的个数

2020-03-26 algorithm

# 我的答案

``````    // 解法1：简单逻辑稍加优化
public List<Integer> countSmaller(int[] nums) {
int[] counts = new int[nums.length];
// 从右往左遍历的好处是，可以重用已经算好的counts[j]值
for (int i = nums.length - 1; i >= 0; i--) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] < nums[i]) {
counts[i]++;
}
// 这里做了一次优化，如果数字相等右面的矮个子数过了，就不用数了直接用
if (nums[i] == nums[j]) {
counts[i] += counts[j];
break;
}
}
}
// 这里也可以用IntStream.of(counts),注意需要用boxed装箱
return Arrays.stream(counts).boxed().collect(Collectors.toList());
}

// 解法2：采用树和递归的思想比较复杂
public class CountSmaller315 {
public static void main(String... args) {
int[] nums = new int[] {5, 2, 6, 1};
CountSmaller315 countSmaller315 = new CountSmaller315();
List<Integer> counted = countSmaller315.countSmaller(nums);
PrintUtil.printline(counted);
}

public List<Integer> countSmaller(int[] nums) {
int n = nums.length;
int[] count = new int[n];
TreeNode treeNode = null;
for (int i = n - 1; i >= 0; i--) {
// 给定值返回比它小的数量是多少，如果在当前节点返回当前节点的smallerCount，如果小于当前节点看它的左子树，如果大于当前节点看右子树
count[i] = countSmaller(treeNode, nums[i]);
// 把值附到节点上，如果当前值小于节点值放左子树（同时 treeNode.smallerCount++），否则放右子树
treeNode = insert(treeNode, nums[i]);
}
return Arrays.stream(count).boxed().collect(Collectors.toList());
}

/*
给定值返回比它小的数量是多少，如果在当前节点返回当前节点的smallerCount，如果小于当前节点看它的左子树，如果大于当前节点看右子树
*/
int countSmaller(TreeNode root, int val) {
if (root == null) {
return 0;
}
if (root.val == val) {
return root.smallerCount;
}
if (val < root.val) {
return countSmaller(root.left, val);
}
return 1 + root.smallerCount + countSmaller(root.right, val);
}

/*
把值附到节点上，如果当前值小于节点值放左子树（同时 root.smallerCount++），否则放右子树
*/
TreeNode insert(TreeNode root, int val) {
if (root == null) {
return new TreeNode(0, val);
}
if (val < root.val) {
root.left = insert(root.left, val);
root.smallerCount++;
return root;
} else {
root.right = insert(root.right, val);
return root;
}
}

/*
树的节点，节点附带比它小节点的数量
*/
private static class TreeNode {
int smallerCount;
int val;
TreeNode left;
TreeNode right;

public TreeNode(int smallerCount, int val) {
this.smallerCount = smallerCount;
this.val = val;
}
}
}
``````

# 我的分析

## leetcode 1137. N-th Tribonacci Number

2020-03-26 algorithm

# 题目：获取斐波那契数列,实际上是triple吹波那契数列:)

The Tribonacci sequence Tn is defined as follows: T0 = 0, T1 = 1, T2 = 1, and Tn+3 = Tn + Tn+1 + Tn+2 for n >= 0. Given n, return the value of Tn.

``````Example 1:
Input: n = 4
Output: 4
Explanation:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4
Example 2:
Input: n = 25
Output: 1389537
``````

Constraints: 0 <= n <= 37 The answer is guaranteed to fit within a 32-bit integer, ie. answer <= 2^31 - 1.

# 我的答案

``````    public static int tribonacci(int n) {
switch (n) {
case 0:
return 0;
case 1:
case 2:
return 1;
default:
return tribonacci(n - 1) + tribonacci(n - 2) + tribonacci(n - 3);
}
}
// 下面这个效率更高
public int tribonacci(int n) {
if (n < 2) return n;
int a = 0, b = 1, c = 1, d;
while (n-- > 2) {
d = a + b + c;
a = b;
b = c;
c = d;
}
return c;
}
``````

# 我的分析

## leetcode 65. Valid Number

2020-03-26 algorithm

# 题目：判断有效数字

Validate if a given string can be interpreted as a decimal number.

``````Some examples:
"0" => true
" 0.1 " => true
"abc" => false
"1 a" => false
"2e10" => true
" -90e3   " => true
" 1e" => false
"e3" => false
" 6e-1" => true
" 99e2.5 " => false
"53.5e93" => true
" --6 " => false
"-+3" => false
"95a54e53" => false
``````

Note: It is intended for the problem statement to be ambiguous. You should gather all requirements up front before implementing one. However, here is a list of characters that can be in a valid decimal number:

Numbers 0-9
Exponent - “e”
Positive/negative sign - “+”/”-“
Decimal point - “.”
Of course, the context of these characters also matters in the input.

# 我的答案

``````    public static boolean isNumber(String s) {
String regex = "^[+-]?((\\d*\\.?\\d+)|(\\d+\\.?\\d*))(e[+-]?\\d+)?\$";
return s.trim().matches(regex);
}
``````

# 我的分析

## leetcode 938. Range Sum of BST

2020-03-26 algorithm

# 题目：在二叉搜索树里找符合条件的节点值并求和

Given the root node of a binary search tree, return the sum of values of all nodes with value between L and R (inclusive). The binary search tree is guaranteed to have unique values.

``````Example 1:
Input: root = [10,5,15,3,7,null,18], L = 7, R = 15
Output: 32
Example 2:
Input: root = [10,5,15,3,7,13,18,1,null,6], L = 6, R = 10
Output: 23
``````

Note: The number of nodes in the tree is at most 10000. The final answer is guaranteed to be less than 2^31.

# 我的答案

``````    public int rangeSumBST(TreeNode root, int L, int R) {
if (root == null) {
return 0;
}
int valToAdd = (root.val < L || root.val > R) ? 0 : root.val;
return valToAdd + rangeSumBST(root.left, L, R) + rangeSumBST(root.right, L, R);
}
``````

# 我的分析

## clarify the concept of recursion

2020-03-09 algorithm

# Clarify the concept of recursion

What’s the difference and connections between recursion, divide-and-conquer algorithm, dynamic programming, and greedy algorithm? If you haven’t made it clear. Doesn’t matter! I would give you a brief introduction to kick off this section.

Recursion is a programming technique. It’s a way of thinking about solving problems. There’re two algorithmic ideas to solve specific problems: divide-and-conquer algorithm and dynamic programming. They’re largely based on recursive thinking (although the final version of dynamic programming is rarely recursive, the problem-solving idea is still inseparable from recursion). There’s also an algorithmic idea called greedy algorithm which can efficiently solve some more special problems. And it’s a subset of dynamic programming algorithms.

The divide-and-conquer algorithm will be explained in this section. Taking the most classic merge sort as an example, it continuously divides the unsorted array into smaller sub-problems. This is the origin of the word divide and conquer. Obviously, the sub-problems decomposed by the ranking problem are non-repeating. If some of the sub-problems after decomposition are duplicated (the nature of overlapping sub-problems), then the dynamic programming algorithm is used to solve them!

## Recursion in detail

Before introducing divide and conquer algorithm, we must first understand the concept of recursion.

The basic idea of recursion is that a function calls itself directly or indirectly, which transforms the solution of the original problem into many smaller sub-problems of the same nature. All we need is to focus on how to divide the original problem into qualified sub-problems, rather than study how this sub-problem is solved. The difference between recursion and enumeration is that enumeration divides the problem horizontally and then solves the sub-problems one by one, but recursion divides the problem vertically and then solves the sub-problems hierarchily.

The following illustrates my understanding of recursion. If you don’t want to read, please just remember how to answer these questions:

1. How to sort a bunch of numbers? Answer: Divided into two halves, first align the left half, then the right half, and finally merge. As for how to arrange the left and right half, please read this sentence again.
2. How many hairs does Monkey King have? Answer: One plus the rest.
3. How old are you this year? Answer: One year plus my age of last year, I was born in 1999.

Two of the most important characteristics of recursive code: end conditions and self-invocation. Self-invocation is aimed at solving sub-problems, and the end condition defines the answer to the simplest sub-problem.

``````int func(How old are you this year) {
// simplest sub-problem, end condition
if (this year equals 1999) return my age 0;
// self-calling to decompose problem
return func(How old are you last year) + 1;
}
``````

Actually think about it, what is the most successful application of recursion? I think it’s mathematical induction. Most of us learned mathematical induction in high school. The usage scenario is probably: we can’t figure out a summation formula, but we tried a few small numbers which seemed containing a kinda law, and then we compiled a formula. We ourselves think it shall be the correct answer. However, mathematics is very rigorous. Even if you’ve tried 10,000 cases which are correct, can you guarantee the 10001th correct? This requires mathematical induction to exert its power. Assuming that the formula we compiled is true at the kth number, furthermore if it is proved correct at the k + 1th, then the formula we have compiled is verified correct.

So what is the connection between mathematical induction and recursion? We just said that the recursive code must have an end condition. If not, it will fall into endless self-calling hell until the memory exhausted. The difficulty of mathematical proof is that you can try to have a finite number of cases, but it is difficult to extend your conclusion to infinity. Here you can see the connection-infinite.

The essence of recursive code is to call itself to solve smaller sub-problems until the end condition is reached. The reason why mathematical induction is useful is to continuously increase our guess by one, and expand the size of the conclusion, without end condition. So by extending the conclusion to infinity, the proof of the correctness of the guess is completed.

### Why learn recursion

First to train the ability to think reversely. Recursive thinking is the thinking of normal people, always looking at the problems in front of them and thinking about solutions, and the solution is the future tense; Recursive thinking forces us to think reversely, see the end of the problem, and treat the problem-solving process as the past tense.

Second, practice analyzing the structure of the problem. When the problem can be broken down into sub problems of the same structure, you can acutely find this feature, and then solve it efficiently.

Third, go beyond the details and look at the problem as a whole. Let’s talk about merge and sort. In fact, you can divide the left and right areas without recursion, but the cost is that the code is extremely difficult to understand. Take a look at the code below (merge sorting will be described later. You can understand the meaning here, and appreciate the beauty of recursion).

``````void sort(Comparable[] a){
int N = a.length;
// So complicated! It shows disrespect for sorting. I refuse to study such code.
for (int sz = 1; sz < N; sz = sz + sz)
for (int lo = 0; lo < N - sz; lo += sz + sz)
merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));
}

/* I prefer recursion, simple and beautiful */
void sort(Comparable[] a, int lo, int hi) {
if (lo >= hi) return;
int mid = lo + (hi - lo) / 2;
sort(a, lo, mid); // soft left part
sort(a, mid + 1, hi); // soft right part
merge(a, lo, mid, hi); // merge the two sides
}

``````

Looks simple and beautiful is one aspect, the key is very interpretable: sort the left half, sort the right half, and finally merge the two sides. The non-recursive version looks unintelligible, full of various incomprehensible boundary calculation details, is particularly prone to bugs and difficult to debug. Life is short, i prefer the recursive version.

Obviously, sometimes recursive processing is efficient, such as merge sort, sometimes inefficient, such as counting the hair of Monkey King, because the stack consumes extra space but simple inference does not consume space. Example below gives a linked list header and calculate its length:

``````/* Typical recursive traversal framework requires extra space O(1) */
public int size(Node head) {
int size = 0;
for (Node p = head; p != null; p = p.next) size++;
return size;
}
/* I insist on recursion facing every problem. I need extra space O(N) */
public int size(Node head) {
if (head == null) return 0;
return size(head.next) + 1;
}
``````

### Tips for writing recursion

My point of view: Understand what a function does and believe it can accomplish this task. Don’t try to jump into the details. Do not jump into this function to try to explore more details, otherwise you will fall into infinite details and cannot extricate yourself. The human brain carries tiny sized stack!

Let’s start with the simplest example: traversing a binary tree.

``````void traverse(TreeNode* root) {
if (root == nullptr) return;
traverse(root->left);
traverse(root->right);
}
``````

Above few lines of code are enough to wipe out any binary tree. What I want to say is that for the recursive function `traverse (root)`, we just need to believe: give it a root node `root`, and it can traverse the whole tree. Since this function is written for this specific purpose, so we just need to dump the left and right nodes of this node to this function, because I believe it can surely complete the task. What about traversing an N-fork tree? It’s too simple, exactly the same as a binary tree!

``````void traverse(TreeNode* root) {
if (root == nullptr) return;
for (child : root->children)
traverse(child);
}
``````

As for pre-order, mid-order, post-order traversal, they are all obvious. For N-fork tree, there is obviously no in-order traversal.

The following explains a problem from LeetCode in detail: Given a binary tree and a target value, the values in every node is positive or negative, return the number of paths in the tree that are equal to the target value, let you write the pathSum function:

``````/* from LeetCode PathSum III： https://leetcode.com/problems/path-sum-iii/ */
root = [10,5,-3,3,2,null,11,3,-2,null,1],
sum = 8

10
/  \
5   -3
/ \    \
3   2   11
/ \   \
3  -2   1

Return 3. The paths that sum to 8 are:

1.  5 -> 3
2.  5 -> 2 -> 1
3. -3 -> 11
``````
``````/* It doesn’t matter if you don’t understand, there is a more detailed analysis version below, which highlights the conciseness and beauty of recursion. */
int pathSum(TreeNode root, int sum) {
if (root == null) return 0;
return count(root, sum) +
pathSum(root.left, sum) + pathSum(root.right, sum);
}
int count(TreeNode node, int sum) {
if (node == null) return 0;
return (node.val == sum) +
count(node.left, sum - node.val) + count(node.right, sum - node.val);
}
``````

The problem may seem complicated, but the code is extremely concise, which is the charm of recursion. Let me briefly summarize the solution process of this problem:

First of all, it is clear that to solve the problem of recursive tree, you must traverse the entire tree. So the traversal framework of the binary tree (recursively calling the function itself on the left and right children) must appear in the main function pathSum. And then, what should they do for each node? They should see how many eligible paths they and their little children have under their feet. Well, this question is clear.

According to the techniques mentioned earlier, define what each recursive function should do based on the analysis just now:

PathSum function: Give it a node and a target value. It returns the total number of paths in the tree rooted at this node and the target value.

Count function: Give it a node and a target value. It returns a tree rooted at this node, and can make up the total number of paths starting with the node and the target value.

``````/* With above tips, comment out the code in detail */
int pathSum(TreeNode root, int sum) {
if (root == null) return 0;
int pathImLeading = count(root, sum); // Number of paths beginning with itself
int leftPathSum = pathSum(root.left, sum); // The total number of paths on the left (Believe he can figure it out)
int rightPathSum = pathSum(root.right, sum); // The total number of paths on the right (Believe he can figure it out)
return leftPathSum + rightPathSum + pathImLeading;
}
int count(TreeNode node, int sum) {
if (node == null) return 0;
// Can I stand on my own as a separate path?
int isMe = (node.val == sum) ? 1 : 0;
// Left brother, how many sum-node.val can you put together?
int leftBrother = count(node.left, sum - node.val);
// Right brother, how many sum-node.val can you put together?
int rightBrother = count(node.right, sum - node.val);
return  isMe + leftBrother + rightBrother; // all count i can make up
}
``````

Again, understand what each function can do and trust that they can do it.

In summary, the binary tree traversal framework provided by the PathSum function calls the count function for each node during the traversal. Can you see the pre-order traversal (the order is the same for this question)? The count function is also a binary tree traversal, used to find the target value path starting with this node. Understand it deeply!

## Divide and conquer algorithm

Merge and sort, typical divide-and-conquer algorithm; divide-and-conquer, typical recursive structure.

The divide-and-conquer algorithm can go in three steps: decomposition-> solve-> merge

1. Decompose the original problem into sub-problems with the same structure.
2. After decomposing to an easy-to-solve boundary, perform a recursive solution.
3. Combine the solutions of the subproblems into the solutions of the original problem.

To merge and sort, let’s call this function `merge_sort`. According to what we said above, we must clarify the responsibility of the function, that is, sort an incoming array. OK, can this problem be solved? Of course! Sorting an array is just the same to sorting the two halves of the array separately, and then merging the two halves.

``````void merge_sort(an array) {
if (some tiny array easy to solve) return;
merge_sort(left half array);
merge_sort(right half array);
merge(left half array, right half array);
}
``````

Well, this algorithm is like this, there is no difficulty at all. Remember what I said before, believe in the function’s ability, and pass it to him half of the array, then the half of the array is already sorted. Have you found it’s a binary tree traversal template? Why it is postorder traversal? Because the routine of our divide-and-conquer algorithm is decomposition-> solve (bottom)-> merge (backtracking) Ah, first left and right decomposition, and then processing merge, backtracking is popping stack, which is equivalent to post-order traversal. As for the `merge` function, referring to the merging of two ordered linked lists, they are exactly the same, and the code is directly posted below.

Let’s refer to the Java code in book `Algorithm 4` below, which is pretty. This shows that not only algorithmic thinking is important, but coding skills are also very important! Think more and imitate more.

``````public class Merge {
// Do not construct new arrays in the merge function, because the merge function will be called multiple times, affecting performance.Construct a large enough array directly at once, concise and efficient.
private static Comparable[] aux;

public static void sort(Comparable[] a) {
aux = new Comparable[a.length];
sort(a, 0, a.length - 1);
}

private static void sort(Comparable[] a, int lo, int hi) {
if (lo >= hi) return;
int mid = lo + (hi - lo) / 2;
sort(a, lo, mid);
sort(a, mid + 1, hi);
merge(a, lo, mid, hi);
}

private static void merge(Comparable[] a, int lo, int mid, int hi) {
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++)
aux[k] = a[k];
for (int k = lo; k <= hi; k++) {
if      (i > mid)              { a[k] = aux[j++]; }
else if (j > hi)               { a[k] = aux[i++]; }
else if (less(aux[j], aux[i])) { a[k] = aux[j++]; }
else                           { a[k] = aux[i++]; }
}
}

private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
}
``````

LeetCode has a special exercise of the divide-and-conquer algorithm. Copy the link to web browser to have a try:

https://leetcode.com/tag/divide-and-conquer/

## design patterns for exception or error handling

2019-12-15 java

1. Don’t manage business logic with exceptions. Use conditional statements instead. If a control can be done with if-else statement clearly, don’t use exceptions because it reduces readability and performance (e.g. null control, divide by zero control). .
2. Exception names must be clear and meaningful, stating the causes of exception.
3. Throw exceptions for error conditions while implementing a method. E.g. if you return -1, -2, -3 etc. values instead of FileNotFoundException, that method can not be understand.
4. Catch specific exceptions instead of the top Exception class. This will bring additional performance, readability and more specific exception handling.
5. Null control with conditionals is not an alternative to catching NullPointerException. If a method may return null, control it with if-else statement. If a return may throw NullPointerException, catch it.
6. Try not to re-throw the exception because of the price. Bu if re-throwing had been a must, re-throw the same exception instead of creating a new exception. This will bring additional performance. You may add additional info in each layer to that exception.
7. Define your own exception hierarchy by extending current Exception class (e.g. UserException, SystemException and their sub types) and use them. By doing this you can specialize your exceptions and define a reusable module/layer of exceptions.
8. Use types of exceptions clearly. Fatal: System crash states. Error: Lack of requirement. Warn: Not an error but error probability. Info: Info for user. Debug: Info for developer.
9. Don’t absorb exceptions with no logging and operation. Ignoring exceptions will save that moment but will create a chaos for maintainability later.
10. Don’t log the same exception more than once. This will provide clearness of the exception location.
11. Always clean up resources (opened files etc.) and perform this in “finally” blocks.
12. Exception handling inside a loop is not recommended for most cases. Surround the loop with exception block instead.
13. Granularity is very important. One try block must exist for one basic operation. So don’t put hundreds of lines in a try-catch statement.
14. Produce enough documentation for your exception definitions (at least JavaDoc).
15. Giving a number/code for each different exception message is a good practice for documentation and faster communication.

## Oracle and Mysql varchar length

2018-11-11 database

One of my collegues come across the problem. I gived her the answer and conclude it here!

# MySQL 列长度

• 5.0.3版本之前：0~255 bytes
• 5.0.3版本及之后：0~65535 bytes，注意又分以下三种
• 上限VARCHAR(65535) //单字节
• 上限VARCHAR(21844) //utf8 编码方式
• 上限VARCHAR(16383) //utf8mb4 编码方式

# Oracle 列长度

• Oracle 10g/11g varchar2 取值范围: 1≤LENGTH≤4000 bytes/characters
• Oracle 12c 有两个版本 varchar2 取值范围:
• STANDARD版 1≤LENGTH≤4000 bytes/characters
• EXTENDED版 1≤LENGTH≤32767 bytes/characters #### ms17

Software Engineer and Full Stack Developer, from Shanghai, China.